8

I am writing a bash script that takes in some optional parameters. I want to translate them and pass them to another script. However, I'm having a hard time passing the optional parameters gracefully.

Here's a outline of what I managed to get working in pseudocode:

a.sh:

if arg1 in arguments; then
    firstArg="first argument"
fi
if arg2 in arguments; then
    secondArg="second argument"
fi

./b.sh $firstArg $secondArg "default argument"

Note the spaces in the arguments.

b.sh:

for arg in "$@"
do
    echo $arg
done

I want to call b.sh, optionally with firstArg and secondArg and a default argument like so:

./b.sh $firstArg $secondArg "default argument"

The problem with this is that if $firstArg or $secondArg are strings with spaces, they will be represented as multiple arguments, and the output will be something like:

first
argument
second
argument
default argument

Okay, that's easy to fix, let's capture the entire string of the arguments by adding quotes around it like so:

./b.sh "$firstArg" "$secondArg" "defaultArg"

Problem is if, for example, firstArg is not set, it results in a blank line (as it will interpret "" as a parameter), so the output will be something like:

(blank line here)
second argument
defaultArg

I've also tried constructing a string and passing it to the shell script, but it doesn't seem to work that way either (it interprets the whole string as an argument, even if I add separate the arguments with quotes).

Note that calling b.sh from my command line with the arguments quoted works fine. Is there a way to mimic how this works from within a bash script?

Andrew
  • 1,355
  • 2
  • 13
  • 28
  • 1
    Aside: Just because I'm copying your lead and using `.sh` extensions on executables (that is, files with the +x permission against which an `execv()`-family call will succeed) doesn't mean it's a good idea. In general, commands in UNIX shouldn't have any extensions, no matter whether they're provided via a shell script, a Python script, a compiled binary, etc. – Charles Duffy May 06 '15 at 20:15
  • Can you explicitly clarify whether this is for code using `#!/bin/sh` or `#!/bin/bash`? – Charles Duffy May 06 '15 at 20:21

2 Answers2

8

If you literally want to copy all arguments given, but add one more:

# this works in any POSIX shell
./b.sh "$@" defaultArg

Alternately, to explicitly pass firstArg and secondArg, but only if they exist (note that set-to-an-empty-value counts as "existing" here):

# this works in any POSIX shell
./b.sh ${firstArg+"$firstArg"} ${secondArg+"$secondArg"} defaultArg

If you want to treat set-to-an-empty-value as not existing:

# this works in any POSIX shell
./b.sh ${firstArg:+"$firstArg"} ${secondArg:+"$secondArg"} defaultArg

An alternate approach is to build up an array of arguments:

# this requires bash or another shell with arrays and append syntax
# be sure any script using this starts with #!/bin/bash
b_args=( )
[[ $firstArg ]] && b_args+=( "$firstArg" )
[[ $secondArg ]] && b_args+=( "$secondArg" )
b_args+=( "default argument" )
./b.sh "${b_args[@]}"

If you want something with the same flexibility as the array method, but without the compatibility issues, define a function; within it, you can safely override "$@" without impacting the rest of the script:

runB() {
  set --
  [ -n "$firstArg" ]  && set -- "$@" "$firstArg"
  [ -n "$secondArg" ] && set -- "$@" "$secondArg"
  ./b.sh "$@" "default argument"
}
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Isn't is quoting `"$@"` wrong here? It will not being subject of word splitting after that anymore.. isn't it? – hek2mgl May 06 '15 at 20:14
  • Good answer! I didn't got the question correctly when posting the comment. – hek2mgl May 06 '15 at 20:28
  • Thank you for your answer! The second and third blocks work fine. Was really scratching my head over this one. – Andrew May 06 '15 at 20:35
  • Does the fourth form _not_ work for you? Unless you're using a very old bash (pre-3.2), that's actually somewhat surprising. Could you describe how it fails? – Charles Duffy May 06 '15 at 20:36
  • I just haven't tried it, I'll try it out and see if it works. – Andrew May 06 '15 at 20:40
  • BTW, if you _do_ need to be compatible with a bash with arrays but not append support (such as something in the 2.x series), then `b_args+=( "$firstArg" )` would become the less efficient and more verbose `b_args=( "${b_args[@]}" "$firstArg" )`. – Charles Duffy May 06 '15 at 20:45
  • I can confirm that the array method works. Thanks for all the help Charles and anyone else who answered. You seem like a real master of shell scripting! – Andrew May 06 '15 at 20:49
1

Use an array:

args=()

if [ ... ]; then
    args+=( "first argument" )
fi

if [ ... ]; then
    args+=( "second argument" )
fi

./b.sh "${args[@]}" "default argument"
Adrian Frühwirth
  • 42,970
  • 10
  • 60
  • 71
  • @EtanReisner No it doesn't because in OP's example `$firstArg` might not be set at all (but used) but if it does get set it will get set to a string literal (same in the above snippet). – Adrian Frühwirth May 06 '15 at 20:25
  • Ah, I assumed those were placeholders for the actual first and second arguments and not literal strings (in the OP and also here) and since you left out the actual tests on the arguments themselves I didn't fill those in. As written this doesn't have that problem but as written this also is only half of a solution (and I think doesn't quite solve it correctly). – Etan Reisner May 06 '15 at 20:30
  • I get where you're coming from on this one. However, the OP giving `./b.sh "$firstArg" "$secondArg" "defaultArg"` as a possible solution (and describing its only fault as passing through empty arguments rather than not providing them at all) indicates that the use of literal strings `"first argument"` and `"second argument"` was just sloppiness earlier in the question. – Charles Duffy May 06 '15 at 20:53
  • @CharlesDuffy I see; I'll admit I probably didn't fully get the question then but then again, this is just a matter of how the `if`s are constructed seeing you also suggested this as a solution. Unless I'm still missing something :-) – Adrian Frühwirth May 07 '15 at 06:27