Translate

Archives

Printing Bash or Korn Shell Script Arguments

Often times when debugging a Bash or a Korn shell script, you may wish to print out the arguments (AKA positional parameters) that you are passing to the shell script as shown in the following trivial example:

$ cat demo.sh
#!/bin/bash
echo "$@"

$ ./demo.sh one two three
one two three
$


Unfortunately, the above script will give unexpected output in a numbers of cases. For example:

$ ./demo.sh -n -e -v -e -r
-v -e -r$


This is due to an “naive” use of the echo shell builtin or utility.

The echo shell builtin and utility supports a number of command line options including -n and -e. Use of the -n option means do not output a trailing newline. Use of the -e option means interpret backslash escapes. These two options are being consumed by echo in the above example.

One “trick” you can use to inhibit this particular behavior is shown in the following example:

$ cat demo.sh
#!/bin/bash
echo "" "$@"

$ ./demo.sh -n -e -v -e -r
-n -e -v -e -r
$


Instead of using $@, lets see what happens if we use $* instead.

$ cat demo.sh
#!/bin/bash
echo $*

$ ./demo.sh -n -e -v -e -r "F P M"
-v -e -r F P M$


What is the difference between $@ and $*? From the Bash manual:

$@ expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands to a separate word. That is, “$@” is equivalent to “$1” “$2” … If the double-quoted expansion occurs within a word, the expansion of the first parameter is joined with the beginning part of the original word, and the expansion of the last parameter is joined with the last part of the original word. When there are no positional parameters, “$@” and $@ expand to nothing (i.e., they are removed).

$* expands to the positional parameters, starting from one. When the expansion occurs within double quotes, it expands to a single word with the value of each parameter separated by the first character of the IFS special variable. That is, “$*” is equivalent to “$1c$2c…”, where c is the first character of the value of the IFS variable. If IFS is unset, the parameters are separated by spaces. If IFS is null, the parameters are joined without intervening separators.

Lets see what happens when we put double quotes around $*.

$ cat demo.sh
#!/bin/bash
echo "$*"

$ ./demo.sh -n -e -v -e -r "F P M"
-n -e -v -e -r F P M
$


You may think you could use “–“, i.e. a double dash, to indicate that you are finished passing options to the echo shell builtin or utility, and what follows are arguments (parameters) that you are passing to the echo command.

Lets see what happens:

$ cat demo.sh
#!/bin/bash
echo -- "$@"

./demo.sh -n -e -v -e -r
-- -n -e -v -e -r
$


The double dash in the output is by design. The echo shell builtin and utility are mandated not to recognize a “–” argument in the manner specified by Guideline 10 of the XBD Utility Syntax Guidelines. Instead “–” is recognized as a string operand.

By the way, $@ is the default list to iterate over in Bash as shown in the following example:

$ cat demo.sh
#!/bin/bash

# note - no use of "$@" or "$*"
for arg
do
    echo "$arg"
done

$ ./demo.sh one two three
one
two
three
$


IEEE Std 1003.1-2017 (POSIX.1) states in the echo Application Usage section that it “is not possible to use echo portably across all POSIX systems unless both -n (as the first argument) and escape sequences are omitted.”

Because of this and the many well-documented portability issues with echo, a better solution is to use printf.

$ cat demo.sh
#!/bin/bash

printf "%s " "$@"
printf "\n"
$
$ ./demo.sh -n -e -v -e -r
-n -e -v -e -r 
$


Note that the above solution prints an extra trailing space. If you do not want that trailing space, you could do the following instead:

$ cat demo.sh
#!/bin/bash

printf "%s\n" "$*"
$
$ ./demo.sh -n -e -v -e -r
-n -e -v -e -r 
$


If you wish to print each argument on a separate line, you could do something like this:

$ cat demo.sh
#!/bin/bash

printf '%s\n' "$@" 
$
$ ./demo.sh -n -e -v -e -r
-n
-e
-v
-e
-r
$


Some people like to use a loop to iterate through the arguments as shown in the following example:

$ cat demo.sh
#!/bin/bash

for i in "$*"
do 
   printf '%s ' "$i" 
done
printf '\n'  
$
$ ./demo.sh -n -e -v -e -r "F Murphy"
-n -e -v -e -r F Murphy
$


You can also use a loop and shift to print the arguments as show in the following example:

$ cat demo.sh
#!/bin/bash

argno=1

while (( $# ))
do 
   printf 'Arg %d: %s\n' $argno "$1"
   (( argno++ ))
   shift 
done
$
$ ./demo.sh -n -e -v -e -r
Arg 1: -n
Arg 2: -e
Arg 3: -v
Arg 4: -e
Arg 5: -r
$


For our final example, we shall use an array:

$ cat demo.sh
#!/bin/bash

args=("$@") 
argno=${#args[@]} 
 
for (( i = 0; i < argno; i++))
do 
    printf '%s\n' "${args[i]}" 
done

$ ./demo.sh -n -e -v -e -r "F P M"
-n
-e
-v
-e
-r
F P M
$

Enjoy!

Comments are closed.