What is the difference between shell options -v and -x for debugging? - bash

-x means xtrace and -v means verbose.
When researching, I find that bash -x "expands commands" while -v prints out the entire command to stdout before execution.
The only difference I see when testing this is that bash -v <scriptname> will display comments of each command as well.
Is this really the only difference? Can someone elaborate?

From the manual:
-v Print shell input lines as they are read.
-x Print commands and their arguments as they are executed.
You can see this distinction when looking at how piped commands are handled:
$ set -v
$ echo "Hello World" | sed 's/World/Earth/'
echo "Hello World" | sed 's/World/Earth/'
Hello Earth
versus:
$ set -x
$ echo "Hello World" | sed 's/World/Earth/'
+ sed s/World/Earth/
+ echo 'Hello World'
Hello Earth
Also, it appears that xtrace (-x) uses the $PS4 variable, while verbose (-v) does not.

The outputs are very similar for simple commands, but it seems that set -v (verbose) simply repeats the input line, while set -x (xtrace) shows the command(s) that are run after variable expansions and word splitting, etc. Considering the examples below, it seems to me that it's worth using both when debugging shell scripts: verbose helps navigate the script and show the intention of the commands, while xtrace shows in more detail what is actually being run. I'm going to use set -vx when debugging scripts from now on. The differences become more obvious in examples with variables, command lists, functions, etc:
$ set +vx # both disabled (default for most people)
$ foo=(1 2 '3 three')
$ printf 'foo is %s\n' "${foo[#]}" && echo ''
foo is 1
foo is 2
foo is 3 three
$ set -v # verbose
$ printf 'foo is %s\n' "${foo[#]}" && echo ''
printf 'foo is %s\n' "${foo[#]}" && echo ''
foo is 1
foo is 2
foo is 3 three
$ set +v
set +v
$ set -x # xtrace
$ printf 'foo is %s\n' "${foo[#]}" && echo ''
+ printf 'foo is %s\n' 1 2 '3 three'
foo is 1
foo is 2
foo is 3 three
+ echo ''
$ set +x
+ set +x
$ set -vx # both verbose and xtrace
$ printf 'foo is %s\n' "${foo[#]}" && echo ''
printf 'foo is %s\n' "${foo[#]}" && echo '' # from verbose, input line simply repeated
+ printf 'foo is %s\n' 1 2 '3 three' # from xtrace, command list split, variables substituted
foo is 1
foo is 2
foo is 3 three
+ echo ''
# word splitting behaviour is made more obvious by xtrace
# (note the array is unquoted now)
$ printf 'foo is %s\n' ${foo[#]} && echo ''
printf 'foo is %s\n' ${foo[#]} && echo ''
+ printf 'foo is %s\n' 1 2 3 three # missing single quotes due to word splitting
foo is 1
foo is 2
foo is 3
foo is three # !
+ echo ''
# function with both verbose and xtrace set
$ function bar { printf '%s\n' "${foo[#]}"; }
function bar { printf '%s\n' "${foo[#]}"; } # verbose repeats definition, nothing output by xtrace
$ bar
bar # verbose repeats function call
+ bar # xtrace shows commands run
+ printf '%s\n' 1 2 '3 three'
1
2
3 three
Aside: other shells
The above examples are from bash. In my testing, the output of posix dash for both options is very similar, although of course no arrays are possible in that shell. zsh expands the xtrace output slightly by prepending with a line number, but otherwise behaves very similarly (although here, its different word splitting behaviour is apparent):
$ zsh
% foo=(1 2 '3 three')
% set -vx
% printf 'foo is %s\n' ${foo[#]} && echo ''
printf 'foo is %s\n' ${foo[#]} && echo '' # verbose repeats input command
+zsh:14> printf 'foo is %s\n' 1 2 '3 three' # xtrace prints expanded command with line number
foo is 1
foo is 2
foo is 3 three
+zsh:14> echo ''

Related

Passing arguments between a script and a binary [duplicate]

I am writing a very simple script that calls another script, and I need to propagate the parameters from my current script to the script I am executing.
For instance, my script name is foo.sh and calls bar.sh.
foo.sh:
bar $1 $2 $3 $4
How can I do this without explicitly specifying each parameter?
Use "$#" instead of plain $# if you actually wish your parameters to be passed the same.
Observe:
$ cat no_quotes.sh
#!/bin/bash
echo_args.sh $#
$ cat quotes.sh
#!/bin/bash
echo_args.sh "$#"
$ cat echo_args.sh
#!/bin/bash
echo Received: $1
echo Received: $2
echo Received: $3
echo Received: $4
$ ./no_quotes.sh first second
Received: first
Received: second
Received:
Received:
$ ./no_quotes.sh "one quoted arg"
Received: one
Received: quoted
Received: arg
Received:
$ ./quotes.sh first second
Received: first
Received: second
Received:
Received:
$ ./quotes.sh "one quoted arg"
Received: one quoted arg
Received:
Received:
Received:
For bash and other Bourne-like shells:
bar "$#"
Use "$#" (works for all POSIX compatibles).
[...] , bash features the "$#" variable, which expands to all command-line parameters separated by spaces.
From Bash by example.
I realize this has been well answered but here's a comparison between "$#" $# "$*" and $*
Contents of test script:
# cat ./test.sh
#!/usr/bin/env bash
echo "================================="
echo "Quoted DOLLAR-AT"
for ARG in "$#"; do
echo $ARG
done
echo "================================="
echo "NOT Quoted DOLLAR-AT"
for ARG in $#; do
echo $ARG
done
echo "================================="
echo "Quoted DOLLAR-STAR"
for ARG in "$*"; do
echo $ARG
done
echo "================================="
echo "NOT Quoted DOLLAR-STAR"
for ARG in $*; do
echo $ARG
done
echo "================================="
Now, run the test script with various arguments:
# ./test.sh "arg with space one" "arg2" arg3
=================================
Quoted DOLLAR-AT
arg with space one
arg2
arg3
=================================
NOT Quoted DOLLAR-AT
arg
with
space
one
arg2
arg3
=================================
Quoted DOLLAR-STAR
arg with space one arg2 arg3
=================================
NOT Quoted DOLLAR-STAR
arg
with
space
one
arg2
arg3
=================================
A lot answers here recommends $# or $* with and without quotes, however none seems to explain what these really do and why you should that way. So let me steal this excellent summary from this answer:
+--------+---------------------------+
| Syntax | Effective result |
+--------+---------------------------+
| $* | $1 $2 $3 ... ${N} |
+--------+---------------------------+
| $# | $1 $2 $3 ... ${N} |
+--------+---------------------------+
| "$*" | "$1c$2c$3c...c${N}" |
+--------+---------------------------+
| "$#" | "$1" "$2" "$3" ... "${N}" |
+--------+---------------------------+
Notice that quotes makes all the difference and without them both have identical behavior.
For my purpose, I needed to pass parameters from one script to another as-is and for that the best option is:
# file: parent.sh
# we have some params passed to parent.sh
# which we will like to pass on to child.sh as-is
./child.sh $*
Notice no quotes and $# should work as well in above situation.
#!/usr/bin/env bash
while [ "$1" != "" ]; do
echo "Received: ${1}" && shift;
done;
Just thought this may be a bit more useful when trying to test how args come into your script
If you include $# in a quoted string with other characters the behavior is very odd when there are multiple arguments, only the first argument is included inside the quotes.
Example:
#!/bin/bash
set -x
bash -c "true foo $#"
Yields:
$ bash test.sh bar baz
+ bash -c 'true foo bar' baz
But assigning to a different variable first:
#!/bin/bash
set -x
args="$#"
bash -c "true foo $args"
Yields:
$ bash test.sh bar baz
+ args='bar baz'
+ bash -c 'true foo bar baz'
My SUN Unix has a lot of limitations, even "$#" was not interpreted as desired. My workaround is ${#}. For example,
#!/bin/ksh
find ./ -type f | xargs grep "${#}"
By the way, I had to have this particular script because my Unix also does not support grep -r
Sometimes you want to pass all your arguments, but preceded by a flag (e.g. --flag)
$ bar --flag "$1" --flag "$2" --flag "$3"
You can do this in the following way:
$ bar $(printf -- ' --flag "%s"' "$#")
note: to avoid extra field splitting, you must quote %s and $#, and to avoid having a single string, you cannot quote the subshell of printf.
bar "$#" will be equivalent to bar "$1" "$2" "$3" "$4"
Notice that the quotation marks are important!
"$#", $#, "$*" or $* will each behave slightly different regarding escaping and concatenation as described in this stackoverflow answer.
One closely related use case is passing all given arguments inside an argument like this:
bash -c "bar \"$1\" \"$2\" \"$3\" \"$4\"".
I use a variation of #kvantour's answer to achieve this:
bash -c "bar $(printf -- '"%s" ' "$#")"
Works fine, except if you have spaces or escaped characters. I don't find the way to capture arguments in this case and send to a ssh inside of script.
This could be useful but is so ugly
_command_opts=$( echo "$#" | awk -F\- 'BEGIN { OFS=" -" } { for (i=2;i<=NF;i++) { gsub(/^[a-z] /,"&#",$i) ; gsub(/ $/,"",$i );gsub (/$/,"#",$i) }; print $0 }' | tr '#' \' )
"${array[#]}" is the right way for passing any array in bash. I want to provide a full cheat sheet: how to prepare arguments, bypass and process them.
pre.sh -> foo.sh -> bar.sh.
#!/bin/bash
args=("--a=b c" "--e=f g")
args+=("--q=w e" "--a=s \"'d'\"")
./foo.sh "${args[#]}"
#!/bin/bash
./bar.sh "$#"
#!/bin/bash
echo $1
echo $2
echo $3
echo $4
result:
--a=b c
--e=f g
--q=w e
--a=s "'d'"

Take file as an input and run awk command on it [duplicate]

I am writing a very simple script that calls another script, and I need to propagate the parameters from my current script to the script I am executing.
For instance, my script name is foo.sh and calls bar.sh.
foo.sh:
bar $1 $2 $3 $4
How can I do this without explicitly specifying each parameter?
Use "$#" instead of plain $# if you actually wish your parameters to be passed the same.
Observe:
$ cat no_quotes.sh
#!/bin/bash
echo_args.sh $#
$ cat quotes.sh
#!/bin/bash
echo_args.sh "$#"
$ cat echo_args.sh
#!/bin/bash
echo Received: $1
echo Received: $2
echo Received: $3
echo Received: $4
$ ./no_quotes.sh first second
Received: first
Received: second
Received:
Received:
$ ./no_quotes.sh "one quoted arg"
Received: one
Received: quoted
Received: arg
Received:
$ ./quotes.sh first second
Received: first
Received: second
Received:
Received:
$ ./quotes.sh "one quoted arg"
Received: one quoted arg
Received:
Received:
Received:
For bash and other Bourne-like shells:
bar "$#"
Use "$#" (works for all POSIX compatibles).
[...] , bash features the "$#" variable, which expands to all command-line parameters separated by spaces.
From Bash by example.
I realize this has been well answered but here's a comparison between "$#" $# "$*" and $*
Contents of test script:
# cat ./test.sh
#!/usr/bin/env bash
echo "================================="
echo "Quoted DOLLAR-AT"
for ARG in "$#"; do
echo $ARG
done
echo "================================="
echo "NOT Quoted DOLLAR-AT"
for ARG in $#; do
echo $ARG
done
echo "================================="
echo "Quoted DOLLAR-STAR"
for ARG in "$*"; do
echo $ARG
done
echo "================================="
echo "NOT Quoted DOLLAR-STAR"
for ARG in $*; do
echo $ARG
done
echo "================================="
Now, run the test script with various arguments:
# ./test.sh "arg with space one" "arg2" arg3
=================================
Quoted DOLLAR-AT
arg with space one
arg2
arg3
=================================
NOT Quoted DOLLAR-AT
arg
with
space
one
arg2
arg3
=================================
Quoted DOLLAR-STAR
arg with space one arg2 arg3
=================================
NOT Quoted DOLLAR-STAR
arg
with
space
one
arg2
arg3
=================================
A lot answers here recommends $# or $* with and without quotes, however none seems to explain what these really do and why you should that way. So let me steal this excellent summary from this answer:
+--------+---------------------------+
| Syntax | Effective result |
+--------+---------------------------+
| $* | $1 $2 $3 ... ${N} |
+--------+---------------------------+
| $# | $1 $2 $3 ... ${N} |
+--------+---------------------------+
| "$*" | "$1c$2c$3c...c${N}" |
+--------+---------------------------+
| "$#" | "$1" "$2" "$3" ... "${N}" |
+--------+---------------------------+
Notice that quotes makes all the difference and without them both have identical behavior.
For my purpose, I needed to pass parameters from one script to another as-is and for that the best option is:
# file: parent.sh
# we have some params passed to parent.sh
# which we will like to pass on to child.sh as-is
./child.sh $*
Notice no quotes and $# should work as well in above situation.
#!/usr/bin/env bash
while [ "$1" != "" ]; do
echo "Received: ${1}" && shift;
done;
Just thought this may be a bit more useful when trying to test how args come into your script
If you include $# in a quoted string with other characters the behavior is very odd when there are multiple arguments, only the first argument is included inside the quotes.
Example:
#!/bin/bash
set -x
bash -c "true foo $#"
Yields:
$ bash test.sh bar baz
+ bash -c 'true foo bar' baz
But assigning to a different variable first:
#!/bin/bash
set -x
args="$#"
bash -c "true foo $args"
Yields:
$ bash test.sh bar baz
+ args='bar baz'
+ bash -c 'true foo bar baz'
My SUN Unix has a lot of limitations, even "$#" was not interpreted as desired. My workaround is ${#}. For example,
#!/bin/ksh
find ./ -type f | xargs grep "${#}"
By the way, I had to have this particular script because my Unix also does not support grep -r
Sometimes you want to pass all your arguments, but preceded by a flag (e.g. --flag)
$ bar --flag "$1" --flag "$2" --flag "$3"
You can do this in the following way:
$ bar $(printf -- ' --flag "%s"' "$#")
note: to avoid extra field splitting, you must quote %s and $#, and to avoid having a single string, you cannot quote the subshell of printf.
bar "$#" will be equivalent to bar "$1" "$2" "$3" "$4"
Notice that the quotation marks are important!
"$#", $#, "$*" or $* will each behave slightly different regarding escaping and concatenation as described in this stackoverflow answer.
One closely related use case is passing all given arguments inside an argument like this:
bash -c "bar \"$1\" \"$2\" \"$3\" \"$4\"".
I use a variation of #kvantour's answer to achieve this:
bash -c "bar $(printf -- '"%s" ' "$#")"
Works fine, except if you have spaces or escaped characters. I don't find the way to capture arguments in this case and send to a ssh inside of script.
This could be useful but is so ugly
_command_opts=$( echo "$#" | awk -F\- 'BEGIN { OFS=" -" } { for (i=2;i<=NF;i++) { gsub(/^[a-z] /,"&#",$i) ; gsub(/ $/,"",$i );gsub (/$/,"#",$i) }; print $0 }' | tr '#' \' )
"${array[#]}" is the right way for passing any array in bash. I want to provide a full cheat sheet: how to prepare arguments, bypass and process them.
pre.sh -> foo.sh -> bar.sh.
#!/bin/bash
args=("--a=b c" "--e=f g")
args+=("--q=w e" "--a=s \"'d'\"")
./foo.sh "${args[#]}"
#!/bin/bash
./bar.sh "$#"
#!/bin/bash
echo $1
echo $2
echo $3
echo $4
result:
--a=b c
--e=f g
--q=w e
--a=s "'d'"

Print bash arguments in reverse order

I have to write a script, which will take all arguments and print them in reverse.
I've made a solution, but find it very bad. Do you have a smarter idea?
#!/bin/sh
> tekst.txt
for i in $*
do
echo $i | cat - tekst.txt > temp && mv temp tekst.txt
done
cat tekst.txt
Could do this
for (( i=$#;i>0;i-- ));do
echo "${!i}"
done
This uses the below
c style for loop
Parameter indirect expansion
(${!i}towards the bottom of the page)
And $# which is the number of arguments to the script
you can use this one liner:
echo $# | tr ' ' '\n' | tac | tr '\n' ' '
bash:
#!/bin/bash
for i in "$#"; do
echo "$i"
done | tac
call this script like:
./reverse 1 2 3 4
it will print:
4
3
2
1
Portably and POSIXly, without arrays and working with spaces and newlines:
Reverse the positional parameters:
flag=''; c=1; for a in "$#"; do set -- "$a" ${flag-"$#"}; unset flag; done
Print them:
printf '<%s>' "$#"; echo
Reversing a simple string, by spaces
Simply:
#!/bin/sh
o=
for i;do
o="$i $o"
done
echo "$o"
will work as
./rev.sh 1 2 3 4
4 3 2 1
Or
./rev.sh world! Hello
Hello world!
If you need to output one line by argument
Just replace echo by printf "%s\n":
#!/bin/sh
o=
for i;do
o="$i $o"
done
printf "%s\n" $o
Reversing an array of strings
If your argument could contain spaces, you could use bash arrays:
#!/bin/bash
declare -a o=()
for i;do
o=("$i" "${o[#]}")
done
printf "%s\n" "${o[#]}"
Sample:
./rev.sh "Hello world" print will this
this
will
print
Hello world
As a function (If you're ok to play with eval.
But eval is evil!!
rev() { eval "set --" $(seq -f '"${%g}"' $# -1 1);printf '%s\n' "$#";}
Then
rev Hello\ world print will this
this
will
print
Hello world

Propagate all arguments in a Bash shell script

I am writing a very simple script that calls another script, and I need to propagate the parameters from my current script to the script I am executing.
For instance, my script name is foo.sh and calls bar.sh.
foo.sh:
bar $1 $2 $3 $4
How can I do this without explicitly specifying each parameter?
Use "$#" instead of plain $# if you actually wish your parameters to be passed the same.
Observe:
$ cat no_quotes.sh
#!/bin/bash
echo_args.sh $#
$ cat quotes.sh
#!/bin/bash
echo_args.sh "$#"
$ cat echo_args.sh
#!/bin/bash
echo Received: $1
echo Received: $2
echo Received: $3
echo Received: $4
$ ./no_quotes.sh first second
Received: first
Received: second
Received:
Received:
$ ./no_quotes.sh "one quoted arg"
Received: one
Received: quoted
Received: arg
Received:
$ ./quotes.sh first second
Received: first
Received: second
Received:
Received:
$ ./quotes.sh "one quoted arg"
Received: one quoted arg
Received:
Received:
Received:
For bash and other Bourne-like shells:
bar "$#"
Use "$#" (works for all POSIX compatibles).
[...] , bash features the "$#" variable, which expands to all command-line parameters separated by spaces.
From Bash by example.
I realize this has been well answered but here's a comparison between "$#" $# "$*" and $*
Contents of test script:
# cat ./test.sh
#!/usr/bin/env bash
echo "================================="
echo "Quoted DOLLAR-AT"
for ARG in "$#"; do
echo $ARG
done
echo "================================="
echo "NOT Quoted DOLLAR-AT"
for ARG in $#; do
echo $ARG
done
echo "================================="
echo "Quoted DOLLAR-STAR"
for ARG in "$*"; do
echo $ARG
done
echo "================================="
echo "NOT Quoted DOLLAR-STAR"
for ARG in $*; do
echo $ARG
done
echo "================================="
Now, run the test script with various arguments:
# ./test.sh "arg with space one" "arg2" arg3
=================================
Quoted DOLLAR-AT
arg with space one
arg2
arg3
=================================
NOT Quoted DOLLAR-AT
arg
with
space
one
arg2
arg3
=================================
Quoted DOLLAR-STAR
arg with space one arg2 arg3
=================================
NOT Quoted DOLLAR-STAR
arg
with
space
one
arg2
arg3
=================================
A lot answers here recommends $# or $* with and without quotes, however none seems to explain what these really do and why you should that way. So let me steal this excellent summary from this answer:
+--------+---------------------------+
| Syntax | Effective result |
+--------+---------------------------+
| $* | $1 $2 $3 ... ${N} |
+--------+---------------------------+
| $# | $1 $2 $3 ... ${N} |
+--------+---------------------------+
| "$*" | "$1c$2c$3c...c${N}" |
+--------+---------------------------+
| "$#" | "$1" "$2" "$3" ... "${N}" |
+--------+---------------------------+
Notice that quotes makes all the difference and without them both have identical behavior.
For my purpose, I needed to pass parameters from one script to another as-is and for that the best option is:
# file: parent.sh
# we have some params passed to parent.sh
# which we will like to pass on to child.sh as-is
./child.sh $*
Notice no quotes and $# should work as well in above situation.
#!/usr/bin/env bash
while [ "$1" != "" ]; do
echo "Received: ${1}" && shift;
done;
Just thought this may be a bit more useful when trying to test how args come into your script
If you include $# in a quoted string with other characters the behavior is very odd when there are multiple arguments, only the first argument is included inside the quotes.
Example:
#!/bin/bash
set -x
bash -c "true foo $#"
Yields:
$ bash test.sh bar baz
+ bash -c 'true foo bar' baz
But assigning to a different variable first:
#!/bin/bash
set -x
args="$#"
bash -c "true foo $args"
Yields:
$ bash test.sh bar baz
+ args='bar baz'
+ bash -c 'true foo bar baz'
My SUN Unix has a lot of limitations, even "$#" was not interpreted as desired. My workaround is ${#}. For example,
#!/bin/ksh
find ./ -type f | xargs grep "${#}"
By the way, I had to have this particular script because my Unix also does not support grep -r
Sometimes you want to pass all your arguments, but preceded by a flag (e.g. --flag)
$ bar --flag "$1" --flag "$2" --flag "$3"
You can do this in the following way:
$ bar $(printf -- ' --flag "%s"' "$#")
note: to avoid extra field splitting, you must quote %s and $#, and to avoid having a single string, you cannot quote the subshell of printf.
bar "$#" will be equivalent to bar "$1" "$2" "$3" "$4"
Notice that the quotation marks are important!
"$#", $#, "$*" or $* will each behave slightly different regarding escaping and concatenation as described in this stackoverflow answer.
One closely related use case is passing all given arguments inside an argument like this:
bash -c "bar \"$1\" \"$2\" \"$3\" \"$4\"".
I use a variation of #kvantour's answer to achieve this:
bash -c "bar $(printf -- '"%s" ' "$#")"
Works fine, except if you have spaces or escaped characters. I don't find the way to capture arguments in this case and send to a ssh inside of script.
This could be useful but is so ugly
_command_opts=$( echo "$#" | awk -F\- 'BEGIN { OFS=" -" } { for (i=2;i<=NF;i++) { gsub(/^[a-z] /,"&#",$i) ; gsub(/ $/,"",$i );gsub (/$/,"#",$i) }; print $0 }' | tr '#' \' )
"${array[#]}" is the right way for passing any array in bash. I want to provide a full cheat sheet: how to prepare arguments, bypass and process them.
pre.sh -> foo.sh -> bar.sh.
#!/bin/bash
args=("--a=b c" "--e=f g")
args+=("--q=w e" "--a=s \"'d'\"")
./foo.sh "${args[#]}"
#!/bin/bash
./bar.sh "$#"
#!/bin/bash
echo $1
echo $2
echo $3
echo $4
result:
--a=b c
--e=f g
--q=w e
--a=s "'d'"

Pass ALL Arguments from Bash Script to Another Command [duplicate]

I am writing a very simple script that calls another script, and I need to propagate the parameters from my current script to the script I am executing.
For instance, my script name is foo.sh and calls bar.sh.
foo.sh:
bar $1 $2 $3 $4
How can I do this without explicitly specifying each parameter?
Use "$#" instead of plain $# if you actually wish your parameters to be passed the same.
Observe:
$ cat no_quotes.sh
#!/bin/bash
echo_args.sh $#
$ cat quotes.sh
#!/bin/bash
echo_args.sh "$#"
$ cat echo_args.sh
#!/bin/bash
echo Received: $1
echo Received: $2
echo Received: $3
echo Received: $4
$ ./no_quotes.sh first second
Received: first
Received: second
Received:
Received:
$ ./no_quotes.sh "one quoted arg"
Received: one
Received: quoted
Received: arg
Received:
$ ./quotes.sh first second
Received: first
Received: second
Received:
Received:
$ ./quotes.sh "one quoted arg"
Received: one quoted arg
Received:
Received:
Received:
For bash and other Bourne-like shells:
bar "$#"
Use "$#" (works for all POSIX compatibles).
[...] , bash features the "$#" variable, which expands to all command-line parameters separated by spaces.
From Bash by example.
I realize this has been well answered but here's a comparison between "$#" $# "$*" and $*
Contents of test script:
# cat ./test.sh
#!/usr/bin/env bash
echo "================================="
echo "Quoted DOLLAR-AT"
for ARG in "$#"; do
echo $ARG
done
echo "================================="
echo "NOT Quoted DOLLAR-AT"
for ARG in $#; do
echo $ARG
done
echo "================================="
echo "Quoted DOLLAR-STAR"
for ARG in "$*"; do
echo $ARG
done
echo "================================="
echo "NOT Quoted DOLLAR-STAR"
for ARG in $*; do
echo $ARG
done
echo "================================="
Now, run the test script with various arguments:
# ./test.sh "arg with space one" "arg2" arg3
=================================
Quoted DOLLAR-AT
arg with space one
arg2
arg3
=================================
NOT Quoted DOLLAR-AT
arg
with
space
one
arg2
arg3
=================================
Quoted DOLLAR-STAR
arg with space one arg2 arg3
=================================
NOT Quoted DOLLAR-STAR
arg
with
space
one
arg2
arg3
=================================
A lot answers here recommends $# or $* with and without quotes, however none seems to explain what these really do and why you should that way. So let me steal this excellent summary from this answer:
+--------+---------------------------+
| Syntax | Effective result |
+--------+---------------------------+
| $* | $1 $2 $3 ... ${N} |
+--------+---------------------------+
| $# | $1 $2 $3 ... ${N} |
+--------+---------------------------+
| "$*" | "$1c$2c$3c...c${N}" |
+--------+---------------------------+
| "$#" | "$1" "$2" "$3" ... "${N}" |
+--------+---------------------------+
Notice that quotes makes all the difference and without them both have identical behavior.
For my purpose, I needed to pass parameters from one script to another as-is and for that the best option is:
# file: parent.sh
# we have some params passed to parent.sh
# which we will like to pass on to child.sh as-is
./child.sh $*
Notice no quotes and $# should work as well in above situation.
#!/usr/bin/env bash
while [ "$1" != "" ]; do
echo "Received: ${1}" && shift;
done;
Just thought this may be a bit more useful when trying to test how args come into your script
If you include $# in a quoted string with other characters the behavior is very odd when there are multiple arguments, only the first argument is included inside the quotes.
Example:
#!/bin/bash
set -x
bash -c "true foo $#"
Yields:
$ bash test.sh bar baz
+ bash -c 'true foo bar' baz
But assigning to a different variable first:
#!/bin/bash
set -x
args="$#"
bash -c "true foo $args"
Yields:
$ bash test.sh bar baz
+ args='bar baz'
+ bash -c 'true foo bar baz'
My SUN Unix has a lot of limitations, even "$#" was not interpreted as desired. My workaround is ${#}. For example,
#!/bin/ksh
find ./ -type f | xargs grep "${#}"
By the way, I had to have this particular script because my Unix also does not support grep -r
Sometimes you want to pass all your arguments, but preceded by a flag (e.g. --flag)
$ bar --flag "$1" --flag "$2" --flag "$3"
You can do this in the following way:
$ bar $(printf -- ' --flag "%s"' "$#")
note: to avoid extra field splitting, you must quote %s and $#, and to avoid having a single string, you cannot quote the subshell of printf.
bar "$#" will be equivalent to bar "$1" "$2" "$3" "$4"
Notice that the quotation marks are important!
"$#", $#, "$*" or $* will each behave slightly different regarding escaping and concatenation as described in this stackoverflow answer.
One closely related use case is passing all given arguments inside an argument like this:
bash -c "bar \"$1\" \"$2\" \"$3\" \"$4\"".
I use a variation of #kvantour's answer to achieve this:
bash -c "bar $(printf -- '"%s" ' "$#")"
Works fine, except if you have spaces or escaped characters. I don't find the way to capture arguments in this case and send to a ssh inside of script.
This could be useful but is so ugly
_command_opts=$( echo "$#" | awk -F\- 'BEGIN { OFS=" -" } { for (i=2;i<=NF;i++) { gsub(/^[a-z] /,"&#",$i) ; gsub(/ $/,"",$i );gsub (/$/,"#",$i) }; print $0 }' | tr '#' \' )
"${array[#]}" is the right way for passing any array in bash. I want to provide a full cheat sheet: how to prepare arguments, bypass and process them.
pre.sh -> foo.sh -> bar.sh.
#!/bin/bash
args=("--a=b c" "--e=f g")
args+=("--q=w e" "--a=s \"'d'\"")
./foo.sh "${args[#]}"
#!/bin/bash
./bar.sh "$#"
#!/bin/bash
echo $1
echo $2
echo $3
echo $4
result:
--a=b c
--e=f g
--q=w e
--a=s "'d'"

Resources