Can bash autocompletion span an equal sign - bash

I'm wondering if it's possible to make autocompletion span an = sign. So, for example, I want to type foo BAR=[TAB][TAB], and have it fill in the possible values for BAR. I've tried the following: I have a file called 'bar', as follows:
#!/bin/bash
echo -e "BAR=100\nBAR=110\nBAR=200" | grep "^$2"
And then I do:
~> complete -C bar foo
If I type foo [TAB][TAB], it gives me some possible values for BAR. If, I type foo BAR=[TAB][TAB], it fails (it appends BAR=BAR= to the end of the command). (note, if I type bar 1 BAR=, it gives me a proper list of completions, so this is not an issue with the bar script).
This would be very useful for some scripts I have.

Create a function (in your .bashrc e.g.):
bar()
{
local POS=${COMP_WORDS[COMP_CWORD]}
if [ "${COMP_WORDS[1]}" = "BAR" ] && [ $COMP_CWORD -eq 3 ]; then
COMPREPLY=($(echo -e "100\n110\n200" | grep ^$POS ))
fi
}
and link function to command foo:
complete -F bar foo

Related

Progress Bar While Looping Script

I would like to show a progress bar while running the following code. I'm calling a script within this code that could take a while depending on the variables passed in. I've tried some of the implementations from How to add a progress bar to a shell script? but have not been able to get them to work. In theory it should continue based off of whether or not the process is still running. If it's still running then show a progress bar.
cat $BLKUPLD | tr -d '\r' | while read line;
do
device_name=`echo $line | cut -d "," -f 1`
quantity_num=`echo $line | cut -d "," -f 2`
bash $SCRIPT $device_name $quantity_num
done
Please let me know if you need additional information.
Below is a progress bar script that will run until the bar is filled.
You will want to change the condition of the outermost while loop to instead check whether your $BLKUPLD has completed, and move the rest of your code inside the while loop where indicated (essentially, you may need to change MAX_ITERATIONS to a boolean condition, and play with where to insert the components of your code within this scaffold).
Ideally you would know how much remaining data you had, and could dynamically set MAX_ITERATIONS accordingly as you enter the loop logic; however, you mentioned that you were okay with having an infinitely looping progress bar as well, which might be the way you have to go if you aren't able to pre-define the end point of your script.
The main premise behind this script that differs from the other thread I linked, is that there are no hardcoded progress points: e.g. "[###__________]33%". Instead, the nested while loops serve to dynamically set the number of hashtags, and also dynamically pad the spacing following the hashtags to maintain a consistent span of progress.
#!/bin/sh
MAX_ITERATIONS=10
WHILE_ITERATOR=0
while [ $WHILE_ITERATOR -le $MAX_ITERATIONS ]
# __Add call to process checking script here.__
do
# Appending hashtags to progress bar string.
PROGRESS_BAR="["
HASHTAGS=$WHILE_ITERATOR
HASHTAGS_ITERATOR=0
while [ $HASHTAGS_ITERATOR -le $HASHTAGS ]
do
# Accounting for first pass through outer loop.
if [ $WHILE_ITERATOR -eq 0 ]; then
PROGRESS_BAR+=" #"
else
PROGRESS_BAR+="#"
fi
HASHTAGS_ITERATOR=$((HASHTAGS_ITERATOR+1))
done
# Appending trailing spaces to progress bar string.
SPACES=$((MAX_ITERATIONS-WHILE_ITERATOR-1))
SPACES_ITERATOR=0
while [ $SPACES_ITERATOR -le $SPACES ]
do
PROGRESS_BAR+=" "
SPACES_ITERATOR=$((SPACES_ITERATOR+1))
done
# Closing progress bar screen and adding return esc char.
PROGRESS_BAR+="]\r"
# Setting echo -n to run properly on Unix & Mac
if [ "`echo -n`" = "-n" ]; then
n=""
c="\c"
else
n="-n"
c=""
fi
# Print the progress bar without \n; reprints in place.
echo $n "$PROGRESS_BAR" $c
sleep 1
WHILE_ITERATOR=$((WHILE_ITERATOR+1))
done
# Print final iteration of progress bar string.
echo "$PROGRESS_BAR"

How to use a single bash command like $1 or $2 or $3 as a string of text instead of one word?

How can I use a bashrc command like below
# .bashrc file
# google_speech
say () {
google_speech -l en "$1"
}
as a string of text, since the above code only reads out the first word of the sentence or paragraph i paste.
like for example if i go into terminal and type:
$ say hello friends how are you
then the script only thinks i typed
$ say hello
Try to use "$#" (with double quotes arround) to get all the arguments of your function:
$ declare -f mysearch # Showing the definition of mysearch (provided by your .bashrc)
mysearch ()
{
echo "Searching with keywords : $#"
}
$ mysearch foo bar # execution result
Searching with keywords : foo bar
Function or scripts arguments are like arrays, so you can use:
1) $# / ${#array[#]} to getting the number of arguments / array's elements.
2) $1 / ${array[1]} to getting the first argument / array's element.
3) $# / ${array[#]} to getting all arguments / array's elements.
EDIT: according to chepner's comment:
Using $# inside a larger string can sometimes produce unintended results. Here, the intent is to produce a single word, so it would be better to use $*
And here's a good topic with great answers explaining the differences.
EDIT 2: Do not forget to put double quotes around $# or $*, maybe your google_speach is taking only one arg. Here's a demo to give you a better understanding:
$ mysearch () { echo "Searching with keywords : $1"; }
$ mysearch2 () { mysearch "$*"; }
$ mysearch2 Hello my dear
Searching with keywords : Hello my dear
$ mysearch3 () { mysearch $*; } # missing double quotes
$ mysearch3 Hello my dear
Searching with keywords : Hello # the other arguments are at least ignored (or sometimes the program will fail when he's controlling the args).

Bash: SPACE-triggered completion

In bash, I would like to achieve the following workflow (prompt shown in brackets, _ is the cursor):
Type "foo" ($ foo_)
Press space. At this point:
if foo is a function, say function foo() { printf "hello from foo" }
space is printed ($ foo _)
the function is called and hello from foo is printed after a space - no newline ($ foo hello from foo_)
if foo is not a function, space is printed and that's it ($ foo _)
I have tried:
Making space send 0x0a (return) to the terminal emulator (iTerm2 on Mac). This works except it obviously prints the newline character as well
Using the built-in complete function: complete -F foo -o nospace foo. This works but I have to type foo then SPACE then TAB for hello from foo to be printed inline
I have heard you could somehow embed a \n-eating script into PS1. Really don't know how to get started on that one.
I could also trap a shortcut, such as Ctrl+T, to execute foo - but I'd really like to only press SPACE.
Ideally space would behave like this only for the first word being typed into the terminal. Any help would be appreciated, please save my sanity.
Why in the world I need this: (I'm a geek AND) I've got an emacs-like ido script that I'd like to be invoked when I type cd followed by SPACE.
A way to do this is using -n 1 with read. For example:
foo(){
echo hello from foo
}
string=''
while read -n 1 a ; do
if [ "$a" = "" ] ; then
tpe=`type -t $string`
if [ "$tpe" = "function" ] ; then
$string
fi
string=''
else
string="$string$a"
fi
done

Exporting the full environment to GNU Parallel

I find it somewhat annoying that I cannot use aliases in GNU Parallel:
alias gi="grep -i"
parallel gi bar ::: foo
/bin/bash: gi: command not found
I had somewhat come to terms with that it is just the way it is. But reading Accessing Associative Arrays in GNU Parallel I am starting to think: Does it really have to be this way?
Is is possible to make a bash function, that collects all of the environment into a function, exports that function and calls GNU Parallel, which will then import the environment in the spawned shell using that function?
So I am not talking about a specialized solution for the gi-alias, but a bash function that will take all aliases/functions/variables (without me having to name them explicitly), package those into a function, that can be activated by GNU Parallel.
Something similar to:
env_parallel() {
# [... gather all environment/all aliases/all functions into parallel_environment() ...]
foreach alias in all aliases {
append alias definition to definition of parallel_environment()
}
foreach variable in all variables (including assoc arrays) {
append variable definition to definition of parallel_environment()
# Code somewhat similar to https://stackoverflow.com/questions/24977782/accessing-associative-arrays-in-gnu-parallel
}
foreach function in all functions {
append function definition to definition of parallel_environment()
}
# make parallel_environment visible to GNU Parallel
export -f parallel_environment
# Running parallel_environment will now create an environment with
# all variables/all aliases/all functions set in current state
# (with the exception of the function parallel_environment of course)
# Inside GNU parallel:
# if set parallel_environment(): prepend it to the command to run
`which parallel` "$#"
}
# Set an example alias
alias fb="echo fubar"
# Set an example variable
BAZ=quux
# Make an example function
myfunc() {
echo $BAZ
}
# This will record the current environment including the 3 examples
# put it into parallel_environment
# run parallel_environment (to set the environment)
# use the 3 examples
env_parallel parallel_environment\; fb bar {}\; myfunc ::: foo
# It should give the same output as running:
fb bar foo; myfunc
# Outputs:
# fubar bar foo
# quux
Progress: This seems to be close to what I want activated:
env_parallel() {
export parallel_environment='() {
'"$(echo "shopt -s expand_aliases"; alias;typeset -p | grep -vFf <(readonly);typeset -f)"'
}'
`which parallel` "$#"
}
VAR=foo
myfunc() {
echo $VAR $1
}
alias myf=myfunc
env_parallel parallel_environment';
' myfunc ::: bar # Works (but gives errors)
env_parallel parallel_environment';
' myf ::: bar # Works, but requires the \n after ;
So now I am down to 1 issue:
weed out all the variables that cannot be assigned value (e.g BASH_ARGC)
How do I list those?
GNU Parallel 20140822 implements this. To activate it you will need to run this once (e.g. in .bashrc):
env_parallel() {
export parallel_bash_environment='() {
'"$(echo "shopt -s expand_aliases 2>/dev/null"; alias;typeset -p | grep -vFf <(readonly; echo GROUPS; echo FUNCNAME; echo DIRSTACK; echo _; echo PIPESTATUS; echo USERNAME) | grep -v BASH_;typeset -f)"'
}'
# Run as: env_parallel ...
`which parallel` "$#"
unset parallel_bash_environment
}
And call GNU Parallel as:
env_parallel ...
That should put the myth to rest that it is impossible to export aliases: all you need is a little Behändigkeit (Thanks a lot to #rici for the inspiration).
In principle, it should be possible. But, as usual, there are a lot of details.
First, it is quite possible in bash for a name to be simultaneously a function, a variable (scalar or array) and an alias. Also, the function and the variable can be exported independently.
So there would be a certain ambiguity in env_parallel foo ... in the case that foo has more than one definition. Possibly the best solution would be to detect the situation and report an error, using a syntax like:
env_parallel -a foo -f bar
in order to be more specific, if necessary.
A simpler possibility is to just export the ambiguity, which is what I do below.
So the basic logic to the importer used in env_parallel might be something like this, leaving out lots of error checking and other niceties:
# Helper functions for clarity. In practice, since they are all short,
# I'd probably in-line all of these by hand to reduce name pollution.
get_alias_() { alias "$1" 2>/dev/null; }
get_func_() { declare -f "$1" 2>/dev/null; }
get_var_() { [[ -v "$1" ]] && declare -p "$1" | sed '1s/--\?/-g/'; }
make_importer() {
local name_
export $1='() {
'"$(for name_ in "${#:2}"; do
local got_=()
get_alias_ "$name_" && got_+=(alias)
get_func_ "$name_" && got_+=(function)
get_var_ "$name_" && got_+=(variable)
if [[ -z $got_ ]]; then
echo "Not found: $name_" >>/dev/stderr
elif (( ${#got_[#]} > 1 )); then
printf >>/dev/stderr \
"Ambiguous: %s is%s\n" \
$name_ "$(printf " %s" "${got_[#]}")"
fi
done)"'
}'
}
In practice, there's no real point defining the function in the local environment if the only purpose is to transmit it to a remote shell. It would be sufficient to print the export command. And, while it is convenient to package the import into a function, as in Accessing Associative Arrays in GNU Parallel,
it's not strictly necessary. It does make it a lot easier to pass the definitions through utilities like Gnu parallel, xargs or find, which is what I typically use this hack for. But depending on how you expect to use the definitions, you might be able to simplify the above by simply prepending the list of definitions to the given command. (If you do that, you won't need to fiddle the global flag with the sed in get_var_.)
Finding out what is in the environment
In case it is useful, here is how to get a list of all aliases, functions and variables:
Functions
declare -F | cut -d' ' -f3
Aliases (Note 1)
alias | awk '/^alias /{print substr($2,1,index($2,"=")-1)}'
Variables (Note 1)
declare -p | awk '$1=="declare"{o=(index($3, "="));print o?substr($3,1,o-1):$3}'
In the awk program, you could check for variable type by looking at $2, which will is usually -- but could be -A for an associative array, -a for an array with integer keys, -i for an integer, -x for exported and -r for readonly. (More than one option may apply; -aix is an "exported" (not implemented) integer array.
Note 1
The alias and declare -p commands produce "reusable" output, which could be eval'ed or piped into another bash, so the values are quoted. Unfortunately, the quoting is just good enough for eval; it's not good enough to avoid confusion. It is possible to define, for example:
x='
declare -a FAKE
'
in which case the output of declare -p will include:
declare -x='
declare -a FAKE
'
Consequently, the lists of aliases and variables need to be treated as "possible names": all names will be included, but it might be that everything included is not a name. Mostly that means being sure to ignore errors:
for a in "${_aliases[#]}"; do
if
defn=$(alias $a 2>>/dev/null)
then
# do something with $defn
fi
done
As is often the case, the solution is to use a function, not an alias. You must first export the function (since parallel and bash are both developed by GNU, parallel knows how to deal with functions as exported by bash).
gi () {
grep -i "$#"
}
export -f go
parallel gi bar ::: foo

How do I write one-liner script that inserts the contents of one file to another file?

Say I have file A, in middle of which have a tag string "#INSERT_HERE#". I want to put the whole content of file B to that position of file A. I tried using pipe to concatenate those contents, but I wonder if there is more advanced one-line script to handle it.
$ cat file
one
two
#INSERT_HERE#
three
four
$ cat file_to_insert
foo bar
bar foo
$ awk '/#INSERT_HERE#/{while((getline line<"file_to_insert")>0){ print line };next }1 ' file
one
two
foo bar
bar foo
three
four
cat file | while read line; do if [ "$line" = "#INSERT_HERE#" ]; then cat file_to_insert; else echo $line; fi; done
Use sed's r command:
$ cat foo
one
two
#INSERT_HERE#
three
four
$ cat bar
foo bar
bar foo
$ sed '/#INSERT_HERE#/{ r bar
> d
> }' foo
one
two
foo bar
bar foo
three
four

Resources