concatenate a function with string and execute it - bash

I wanna concatenate a command specified in a function with string and execute it after.
I will simplify my need with an exemple to execute "ls -l -a"
#!/bin/bash
echo -e "specify command"
read command # ls
echo -e "specify argument"
read arg # -l
test () {
$command $arg
}
eval 'test -a'
Except that

Use an array, like this:
args=()
read -r command
args+=( "$command" )
read -r arg
args+=( "$arg" )
"${args[#]}" -a
If you want a function, then you could do this:
run_with_extra_switch () {
"$#" -a
}
run_with_extra_switch "${args[#]}"

#!/bin/bash
echo -e "specify command"
read command # ls
echo -e "specify argument"
read arg # -l
# using variable
fun1 () {
line="$command $arg"
}
# call the function
fun1
# parameter expansion will expand to the command and execute
$line
# or using stdout (overhead)
fun2 () {
echo "$command $arg"
}
# process expansion will execute function in sub-shell and output will be expanded to a command and executed
$(fun2)
It will work for the given question however to understand how it works look at shell expansion and attention must be payed to execute arbitrary commands.
Before to execute the command, it can be prepended by printf '<%s>\n' for example to show what will be executed.

Related

Output bash alias full form before running

I like using bash aliases quite often (I am using .zshrc) but I would prefer that the aliases would show what they do. This is because I have to pair program quite often. I know doing type alias_name and also alias alias_name displays a description. Is there a way to get my aliases to display their full form before they run? I tried prepending my aliases like alias alias_name='type alias_name && ...'. However the output for this would also include the prepended code as expected. Is there a way around it?
In bash and zsh you can define a command that prints and executes its arguments. Then use that command in each of your aliases.
printandexecute() {
{ printf Executing; printf ' %q' "$#"; echo; } >&2
"$#"
}
# instead of `alias name="somecommand arg1 arg2"` use
alias myalias="printandexecute somecommand arg1 arg2"
You can even automatically insert the printandexecute into each alias definition by overriding the alias builtin itself:
printandexecute() {
{ printf Executing; printf ' %q' "$#"; echo; } >&2
"$#"
}
alias() {
for arg; do
[[ "$arg" == *=* ]] &&
arg="${arg%%=*}=printandexecute ${arg#*=}"
builtin alias "$arg"
done
}
# This definition automatically inserts printandexecute
alias myalias="somecommand arg1 arg2"
Example in an an interactive session. $ is the prompt.
$ myalias "string with spaces"
Executing somecommand arg1 arg2 string\ with\ spaces
actual output of somecommand

Prevent variable expansion in the parameter of a function

function kubeall {
for i in `seq 0 2`; do
echo pod-$i
kubectl exec -it pod-$i -- bash -c "$#"
done
}
kubeall "cat ~/logs/pod-$i/log.out"
Is it possible to prevent expansion of the variable ($i in this case) in the parameter itself?
Passing $i to kubeall as-is won't help. You should pass $i to bash as a positional parameter instead.
function kubeall {
for i in {0..2}; do
echo "pod-$i"
kubectl exec -it "pod-$i" -- bash -c "$1" bash "$i"
done
}
kubeall 'cat ~/"logs/pod-$1/log.out"'
Not sure if this what you want, but here it goes:
#!/bin/bash
function kubeall {
echo '$#: '"$#"
for i in $(seq 0 0); do
echo pod-$i
#Note :: Include $i to the new shell.
bash -c "i=$i; $#"
done
}
#Note :: Using single quotes here to send the arguments as it is.
kubeall 'echo ~/pod-$i/log.out'
Output:
$#: echo ~/pod-$i/log.out
pod-0
/home/username/pod-0/log.out

shell script: running cmd that was passed into a function

I have a bash script:
function run_cmd
{
CMD="$#"
echo ">>> $CMD"
exec $CMD
}
CLUSTERS_PKG="abc1, abc2"
# run command
run_cmd "package upload -c '$CLUSTERS_PKG'"
However, when I run this command, I get a usage error with the 'package' command. If I run the command with a copy+paste, it works fine.
It doesn't seem to like me passing in $CLUSTERS_PKG variable with spaces and quotes around it. How do I properly run "everything" that is passed into run_cmd w/o the shell clobbering things?
Be sure to read the link posted by Adrian Frühwirth. This answer is really just a short summary of the advice found there.
If you pass the argument like this:
run_cmd "package upload -c '$CLUSTERS_PKG'"
then you'll have to use eval in your function so that the single quotes are treated as quoting operators:
run_cmd () {
CMD="$#"
echo ">>> $CMD"
eval "$CMD"
}
If you pass the arguments like this:
run_cmd package upload -c "$CLUSTERS_PKG"
then you can use an array in your function (much safer than using eval):
run_cmd () {
CMD=( "$#" )
echo ">>> ${CMD[#]}"
"${CMD[#]}"
}
You don't really need to use exec; in fact, you probably don't, as that causes the current process to be replaced by CMD: you won't return to the shell that called run_cmd after $CMD exits.
You can use BASH ARRAYS:
function run_cmd
{
arr=( "$#" )
echo ">>> ${arr[#]}"
exec "${arr[#]}"
}

escaping sh/bash function arguments

I want to submit multiple commands with arguments to shell functions, and thus quote my commands like his:
$ CMD=''\''CMD'\'' '\''ARG1 ARG2 ARG3'\'''
$ echo $CMD
'CMD' 'ARG1 ARG2 ARG3' 'ARG4'
Now when I try to us them in a function like this:
$ function execute { echo "$1"; echo "$2"; echo "$3"; }
I get the result:
$ execute $CMD
'CMD'
'ARG1
ARG2
How can I get to this result:
$ execute $CMD
CMD
ARG1 AGR2 ARG3
Thanks in advance!
PS: I use an unquoting function like:
function unquote { echo "$1" | xargs echo; }
EDIT:
to make my intentions more clear: I want to gradually build up a command that needs arguments with spaces passed to subfunctions:
$ CMD='HOST '\''HOSTNAME'\'' '\''sh SCRIPTNAME'\'' '\''MOVE '\''\'\'''\''/path/to/DIR1'\''\'\'''\'' '\''\'\'''\''/path/to/DIR2'\''\'\'''\'''\'''
$ function execute { echo "$1 : $2 : $3 : $4"; }
$ execute $CMD
HOST : 'HOSTNAME' : 'sh : SCRIPTNAME'
The third arguments breaks unexpected at a space, the quoting is ignored. ??
Use an array and # in double quotes:
function execute () {
echo "$1"
echo "$2"
echo "$3"
}
CMD=('CMD' 'ARG1 ARG2 ARG3' 'ARG4')
execute "${CMD[#]}"
function execute {
while [[ $# > 0 ]]; do
cmd=$(cut -d' ' -f1 <<< $1)
arg=$(sed 's/[^ ]* //' <<< $1)
echo "$cmd receives $arg"
shift
done
}
CMD1="CMD1 ARG11 ARG12 ARG13"
CMD2="CMD2 ARG21 ARG22 ARG23"
execute "$CMD1" "$CMD2"
Gives:
CMD1 receives ARG11 ARG12 ARG13
CMD2 receives ARG21 ARG22 ARG23

Equivalent of __FILE__ and __LINE__ in Bash

Is there any variable in bash that contains the name of the .sh file executed? The line number would be great too.
I want to use it in error messages such as:
echo "ERROR: [$FILE:L$LINE] $somefile not found"
#!/bin/bash
echo $LINENO
echo `basename $0`
$LINENO for the current line number
$0 for the current file. I used basename to ensure you only get the file name and not the path.
UPDATE:
#!/bin/bash
MY_NAME=`basename $0`
function ouch {
echo "Fail # [${MY_NAME}:${1}]"
exit 1
}
ouch $LINENO
You have to pass the line as a parameter if you use the function approach else you will get the line of the function definition.
I find the "BASH_SOURCE" and "BASH_LINENO" built-in arrays very useful:
$ cat xx
#!/bin/bash
_ERR_HDR_FMT="%.23s %s[%s]: "
_ERR_MSG_FMT="${_ERR_HDR_FMT}%s\n"
error_msg() {
printf "$_ERR_MSG_FMT" $(date +%F.%T.%N) ${BASH_SOURCE[1]##*/} ${BASH_LINENO[0]} "${#}"
}
error_msg "here"
error_msg "and here"
Invoking xx yields
2010-06-16.15:33:13.069 xx[11]: here
2010-06-16.15:33:13.073 xx[14]: and here
You just need to
echo $LINENO
echo $(basename $0)
Here's how to do it in a reusable function. if the following is in a file named script:
#!/bin/bash
debug() {
echo "${BASH_SOURCE[1]##*/}:${FUNCNAME[1]}[${BASH_LINENO[0]}]" > /dev/tty
}
debug
This produces the output:
script:main[5]
Which indicates the line on which debug was called.
The following will print out the filename, function, line and an optional message.
Also works in zsh for extra goodness.
# Say the file, line number and optional message for debugging
# Inspired by bash's `caller` builtin
# Thanks to https://unix.stackexchange.com/a/453153/143394
function yelp () {
# shellcheck disable=SC2154 # undeclared zsh variables in bash
if [[ $BASH_VERSION ]]; then
local file=${BASH_SOURCE[1]##*/} func=${FUNCNAME[1]} line=${BASH_LINENO[0]}
else # zsh
emulate -L zsh # because we may be sourced by zsh `emulate bash -c`
# $funcfiletrace has format: file:line
local file=${funcfiletrace[1]%:*} line=${funcfiletrace[1]##*:}
local func=${funcstack[2]}
[[ $func =~ / ]] && func=source # $func may be filename. Use bash behaviour
fi
echo "${file##*/}:$func:$line $*" > /dev/tty
}

Resources