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
Related
I need to create a file for subsequent nohup execution, the file contains some variables, I don't want the variables to be replaced when I generate through the file.
When I executed the code with the following code, all the variables were replaced in the generated script, which was not what I expected.
#!/bin/bash
gen_script() {
filepath=$1
if [ ! -f "$filepath" ]; then
cat >${filepath} <<EOF
#!/bin/bash
# Code generated by main.sh; DO NOT EDIT.
test(){
ip=$1
port=$2
restorefile=$3
redis-cli -h $ip -p $port --pipe < $restorefile
}
test "$#"
EOF
fi
}
main(){
gen_script exec.sh
nohup bash exec.sh $1 $2 > nohup.out 2>&1 &
}
main "$#"
How can I change my code please? I really appreciate any help with this.
To disable expansions in here document, quote the delimieter:
cat <<'EOF'
... $not_expanded
EOF
Instead, let bash serialize the function.
#!/bin/bash
work() {
ip=$1
port=$2
restorefile=$3
redis-cli -h $ip -p $port --pipe < $restorefile
}
gen_script() {
filepath=$1
if [ ! -f "$filepath" ]; then
cat >${filepath} <<EOF
#!/bin/bash
# Code generated by main.sh; DO NOT EDIT.
$(declare -f work)
work "\$#"
EOF
fi
}
main() {
gen_script exec.sh
nohup bash exec.sh "$1" "$2" > nohup.out 2>&1 &
}
main "$#"
Check your script with shellcheck. Do not define a function named test, there is already a super standard command test which is an alias for command [.
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.
The set command can be used to change values of the positional arguments $1 $2 ...
But, is there any way to change $0 ?
In Bash greater than or equal to 5 you can change $0 like this:
$ cat bar.sh
#!/bin/bash
echo $0
BASH_ARGV0=lol
echo $0
$ ./bar.sh
./bar.sh
lol
ZSH even supports assigning directly to 0:
$ cat foo.zsh
#!/bin/zsh
echo $0
0=lol
echo $0
$ ./foo.zsh
./foo.zsh
lol
Here is another method. It is implemented through direct commands execution which is somewhat better than sourcing (the dot command). But, this method works only for shell interpreter, not bash, since sh supports -s -c options passed together:
#! /bin/sh
# try executing this script with several arguments to see the effect
test ".$INNERCALL" = .YES || {
export INNERCALL=YES
cat "$0" | /bin/sh -s -c : argv0new "$#"
exit $?
}
printf "argv[0]=$0\n"
i=1 ; for arg in "$#" ; do printf "argv[$i]=$arg\n" ; i=`expr $i + 1` ; done
The expected output of the both examples in case ./the_example.sh 1 2 3 should be:
argv[0]=argv0new
argv[1]=1
argv[2]=2
argv[3]=3
#! /bin/sh
# try executing this script with several arguments to see the effect
test ".$INNERCALL" = .YES || {
export INNERCALL=YES
# this method works both for shell and bash interpreters
sh -c ". '$0'" argv0new "$#"
exit $?
}
printf "argv[0]=$0\n"
i=1 ; for arg in "$#" ; do printf "argv[$i]=$arg\n" ; i=`expr $i + 1` ; done
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
I generally have -e set in my Bash scripts, but occasionally I would like to run a command and get the return value.
Without doing the set +e; some-command; res=$?; set -e dance, how can I do that?
From the bash manual:
The shell does not exit if the command that fails is [...] part of any command executed in a && or || list [...].
So, just do:
#!/bin/bash
set -eu
foo() {
# exit code will be 0, 1, or 2
return $(( RANDOM % 3 ))
}
ret=0
foo || ret=$?
echo "foo() exited with: $ret"
Example runs:
$ ./foo.sh
foo() exited with: 1
$ ./foo.sh
foo() exited with: 0
$ ./foo.sh
foo() exited with: 2
This is the canonical way of doing it.
as an alternative
ans=0
some-command || ans=$?
Maybe try running the commands in question in a subshell, like this?
res=$(some-command > /dev/null; echo $?)
Given behavior of shell described at this question it's possible to use following construct:
#!/bin/sh
set -e
{ custom_command; rc=$?; } || :
echo $rc
Another option is to use simple if. It is a bit longer, but fully supported by bash, i.e. that the command can return non-zero value, but the script doesn't exit even with set -e. See it in this simple script:
#! /bin/bash -eu
f () {
return 2
}
if f;then
echo Command succeeded
else
echo Command failed, returned: $?
fi
echo Script still continues.
When we run it, we can see that script still continues after non-zero return code:
$ ./test.sh
Command failed, returned: 2
Script still continues.
Use a wrapper function to execute your commands:
function __e {
set +e
"$#"
__r=$?
set -e
}
__e yourcommand arg1 arg2
And use $__r instead of $?:
if [[ __r -eq 0 ]]; then
echo "success"
else
echo "failed"
fi
Another method to call commands in a pipe, only that you have to quote the pipe. This does a safe eval.
function __p {
set +e
local __A=() __I
for (( __I = 1; __I <= $#; ++__I )); do
if [[ "${!__I}" == '|' ]]; then
__A+=('|')
else
__A+=("\"\$$__I\"")
fi
done
eval "${__A[#]}"
__r=$?
set -e
}
Example:
__p echo abc '|' grep abc
And I actually prefer this syntax:
__p echo abc :: grep abc
Which I could do with
...
if [[ ${!__I} == '::' ]]; then
...