Suppress quote character in bash completion - bash

I am adding bash completion for some parameters of a query that users can enter on the command line to a script. Something like:
foo.py 'select abc(), def
Here I am completing the column names abc() and def. The problem that I am facing is that bash is automatically adding the ending single quote which is a hindrance to users. They might want to continue writing the query. Note that I need to use the single quotes while accepting the query as the query can contain parenthesis.
For example,
foo.py 'select ab<TAB>
should result in:
foo.py 'select abc(
instead of:
foo.py 'select abc('
My completion function returns select abc( but it seems that bash is inserting the extra closing quote that I want to get rid of. Any ideas how to do this?
Completion function used:
_egg_autocomplete_()
{
case "$COMP_CWORD" in
2)
readarray -t COMPREPLY < <( ./suggest.py order $2 )
;;
esac
}
suggest.py contains the actual code which prints suggested completions one per line. The suggestions can contain spaces in them.
I found the following at https://tiswww.case.edu/php/chet/bash/NEWS but was unable to make it work:
j. New application variable, rl_completion_suppress_quote, settable
by an application completion function. If set to non-zero,
readline does not attempt to append a closing quote to a completed
word.
I added the following line to my ~/.inputrc file but it didn't cause any difference:
set rl_completion_suppress_quote 1
Bash version used: GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu)

Related

Using a bash script to insert into an SQL table with $s

I'm using a bash script to make changes to an SQL database. One of the values i'm updating uses dollar signs. The current value being something like "$$$$$" and i need to change it to "$$$$$$$$$$". However, a a $ in a bash script is used for variables.
How can i allow this small section of my bash script to used a $ as a normal character?
function prep() {
DATE_STAMP=$(date +%m%d%Y)
log "Changing mask to 10 characters"
log "$(/opt/CPU/bin/connx-query -q "update TYPE set TYPE.MASK = '$$$$$$$$$$'")"
}
As it stands right now, its just replacing each dollar sign with some random number found earlier in my script.
Bash provides different types of quoting, each with different rules about substitution (single quote ', double quote ", here document/string <<<"string" and and $'.
The double quote (used in the log ... update) will enable variable substitution, replacing each pair of $$ with the current shell PID (looks like random number).
Few options:
Consider quoting each '$' to prevent expansion
log "$(/opt/CPU/bin/connx-query -q "update TYPE set TYPE.MASK = '\$\$\$\$\$\$\$\$\$\$'")"
Over thought my own question. I can just escape the $. '\$\$\$\$\$\$\$\$\$\$'

Append bash parameters and pass forward to other script

I need to pass further original parameters and also I want to add some others. Something like this:
#!/bin/bash
params="-D FOREGROUND"
params+=" -c Include conf/dev.conf"
/usr/local/apache/bin/apachectl $params "$#"
This code above don't work as expected if params contains of two or more parameters, it treated as one parameter.
The code in your example should work if the following command is valid when executed at the command line written exactly like this :
/usr/local/apache/bin/apachectl -D FOREGROUND -c Include conf/dev.conf "$#"
A quick web search leads me to think that what you want is this (notice the additional double quotes) :
/usr/local/apache/bin/apachectl -D FOREGROUND -c "Include conf/dev.conf" "$#"
Here is how to achieve that simply and reliably with arrays, in a way that sidesteps "quoting inside quotes" issues:
#!/bin/bash
declare -a params=()
params+=(-D FOREGROUND)
params+=(-c "Include conf/dev.conf")
/usr/local/apache/bin/apachectl "${params[#]}" "$#"
The params array contains 4 strings ("-D", "FOREGROUND", "-c" and "Include conf/dev/conf"). The array expansion ("${params[#]}", note that the double quotes are important here) expands them to these 4 strings, as if you had written them with double quotes around them (i.e. without further word splitting).
Using arrays with this kind of expansion is a flexible and reliable to way to build commands and then execute them with a simple expansion.
If the issue is the space in the parameter "-c Include conf/dev.conf" then you could just use a backspace to preserve the space character:
params+="-c Include\ conf/dev.conf"

Customizing bash completion output: each suggestion on a new line

When you type something, you often use bash autocompletion: you start writing a command, for example, and you type TAB to get the rest of the word.
As you have probably noticed, when multiple choices match your command, bash displays them like this :
foobar#myserv:~$ admin-
admin-addrsync admin-adduser admin-delrsync admin-deluser admin-listsvn
admin-addsvn admin-chmod admin-delsvn admin-listrsync
I'm looking for a solution to display each possible solution on a new line, similar to the last column on a ls -l. Ever better, it would be perfect if I could apply a rule like this: "if you find less than 10 suggestions, display them one by line, if more => actual display".
bash prior to version 4.2 doesn't allow any control over the output format of completions, unfortunately.
Bash 4.2+ allows switching to 1-suggestion-per-line output globally, as explained in Grisha Levit's helpful answer, which also links to a clever workaround to achieve a per-completion-function solution.
The following is a tricky workaround for a custom completion.
Solving this problem generically, for all defined completions, would be much harder (if there were a way to invoke readline functions directly, it might be easier, but I haven't found a way to do that).
To test the proof of concept below:
Save to a file and source it (. file) in your interactive shell - this will:
define a command named foo (a shell function)
whose arguments complete based on matching filenames in the current directory.
(When foo is actually invoked, it simply prints its argument in diagnostic form.)
Invoke as:
foo [fileNamePrefix], then press tab:
If between 2 and 9 files in the current directory match, you'll see the desired line-by-line display.
Otherwise (1 match or 10 or more matches), normal completion will occur.
Limitations:
Completion only works properly when applied to the LAST argument on the command line being edited.
When a completion is actually inserted in the command line (once the match is unambiguous), NO space is appended to it (this behavior is required for the workaround).
Redrawing the prompt the first time after printing custom-formatted output may not work properly: Redrawing the command line including the prompt must be simulated and since there is no direct way to obtain an expanded version of the prompt-definition string stored in $PS1, a workaround (inspired by https://stackoverflow.com/a/24006864/45375) is used, which should work in typical cases, but is not foolproof.
Approach:
Defines and assigns a custom completion shell function to the command of interest.
The custom function determines the matches and, if their count is in the desired range, bypasses the normal completion mechanism and creates custom-formatted output.
The custom-formatted output (each match on its own line) is sent directly to the terminal >/dev/tty, and then the prompt and command line are manually "redrawn" to mimic standard completion behavior.
See the comments in the source code for implementation details.
# Define the command (function) for which to establish custom command completion.
# The command simply prints out all its arguments in diagnostic form.
foo() { local a i=0; for a; do echo "\$$((i+=1))=[$a]"; done; }
# Define the completion function that will generate the set of completions
# when <tab> is pressed.
# CAVEAT:
# Only works properly if <tab> is pressed at the END of the command line,
# i.e., if completion is applied to the LAST argument.
_complete_foo() {
local currToken="${COMP_WORDS[COMP_CWORD]}" matches matchCount
# Collect matches, providing the current command-line token as input.
IFS=$'\n' read -d '' -ra matches <<<"$(compgen -A file "$currToken")"
# Count matches.
matchCount=${#matches[#]}
# Output in custom format, depending on the number of matches.
if (( matchCount > 1 && matchCount < 10 )); then
# Output matches in CUSTOM format:
# print the matches line by line, directly to the terminal.
printf '\n%s' "${matches[#]}" >/dev/tty
# !! We actually *must* pass out the current token as the result,
# !! as it will otherwise be *removed* from the redrawn line,
# !! even though $COMP_LINE *includes* that token.
# !! Also, by passing out a nonempty result, we avoid the bell
# !! signal that normally indicates a failed completion.
# !! However, by passing out a single result, a *space* will
# !! be appended to the last token - unless the compspec
# !! (mapping established via `complete`) was defined with
# !! `-o nospace`.
COMPREPLY=( "$currToken" )
# Finally, simulate redrawing the command line.
# Obtain an *expanded version* of `$PS1` using a trick
# inspired by https://stackoverflow.com/a/24006864/45375.
# !! This is NOT foolproof, but hopefully works in most cases.
expandedPrompt=$(PS1="$PS1" debian_chroot="$debian_chroot" "$BASH" --norc -i </dev/null 2>&1 | sed -n '${s/^\(.*\)exit$/\1/p;}')
printf '\n%s%s' "$expandedPrompt" "$COMP_LINE" >/dev/tty
else # Just 1 match or 10 or more matches?
# Perform NORMAL completion: let bash handle it by
# reporting matches via array variable `$COMPREPLY`.
COMPREPLY=( "${matches[#]}" )
fi
}
# Map the completion function (`_complete_foo`) to the command (`foo`).
# `-o nospace` ensures that no space is appended after a completion,
# which is needed for our workaround.
complete -o nospace -F _complete_foo -- foo
bash 4.2+ (and, more generally, applications using readline 6.2+) support this with the use of the completion-display-width variable.
The number of screen columns used to display possible matches when performing completion. The value is ignored if it is less than 0 or greater than the terminal screen width. A value of 0 will cause matches to be displayed one per line. The default value is -1.
Run the following to set the behavior for all completions1 for your current session:
bind 'set completion-display-width 0'
Or modify your ~/.inputrc2 file to have:
set completion-display-width 0
to change the behavior for all new shells.
1 See here for a method for controlling this behavior for individual custom completion functions.
2 The search path for the readline init file is $INPUTRC, ~/.inputrc, /etc/inputrc so modify the file appropriate for you.

Can't run simple bash script

I try executed some like this
#!/bin/bash
${postid=41930}
while ${postid} < 42000;
do
`node title.js ${postid}`
${postid} = ${postid} +1;
done
I have this error:
: command not found30
run.sh: line 8: syntax error: unexpected end of file
$ echo $SHELL
/bin/bash
$
From man sh
while *list*;do *list*;done
sh version 3.2
There are several places in your script needs to be fixed:
As chepner said you can't assign value to an evaluated result like ${postid}, instead use postid directly in the left hand side of your assignment
There should be some invisible characters in your script. Try to run dos2unix myscript.sh or try hand typing the the following code into an new file
https://gist.github.com/1651190
sorry i can't format the code correctly, so please check the gist at https://gist.github.com/1651190
Another quick way, using only bash features is:
#!/bin/env bash
for postid in {41930..41999} ; do node title.js ${postid} ; done
References: http://www.gnu.org/software/bash/manual/bashref.html#Brace-Expansion
Probably, you want
for((postid=41930;postid<42000;++postid)) do
node title.js $postid
done
The other answers are probably what you want to use. FYI, here's where your errors came from.
${postid=41930}
To assign 41930 to posted, just use postid=41930. Note there are no spaces around the equals sign!
while ${postid} < 42000;
The {} around postid are optional; $postid works just as well.. You do need to wrap that conditional expansion in a command, as the while loop can't use a bare expression. Something like while [ $postid < 42000 ];. Note that in this case, you must have spaces separating the [ and ] from the rest of the expression.
do
`node title.js ${postid}`
${postid} = ${postid} +1;
In order to assign a value to a variable, bash does not allow spaces around the equal sign. With spaces, it interprets this line by expanding $postid and treating that as a command to run, with = as the first argument. Use postid=$postid + 1;. On the left, no dollar sign is needed, as you are not expanding the value of a variable, but assigning to a name. On the right, you need the dollar sign to get the value of posted.
done

Shell script input containing asterisk

How do I write a shell script (bash on HPUX) that receives a string as an argument containing an asterisk?
e.g. myscript my_db_name "SELECT * FROM table;"
The asterisk gets expanded to all the file names in the current directory, also if I assign a variable like this.
DB_QUERY="$2"
echo $DB_QUERY
The asterisk "*" is not the only character you have to watch out for, there's lots of other shell meta-charaters that can cause problems, like < > $ | ; &
The simple answer is always to put your arguments in quotes (that's the double-quote, " ) when you don't know what they might contain.
For your example, you should write:
DB_QUERY="$2"
echo "$DB_QUERY"
It starts getting awkward when you want your argument to be used as multiple parameters or you start using eval, but you can ask about that separately.
You always need to put double quotes around a variable reference if you want to prevent it from triggering filename expansion. So, in your example, use:
DB_QUERY="$2"
echo "$DB_QUERY"
In the first example, use single quotes:
myscript my_db_name 'SELECT * FROM table;'
In the second example, use double quotes:
echo "$DB_QUERY"

Resources