21
#!/bin/sh
echo "Noise $1"
echo "Enhancement $2"
for snr in 0 5 10 15 20 25
do
  python evaluate.py --noise $1 --snr 25 --iterations 1250 --enhancement $2
done

If $2 is not specified, I don't want to pass the --enhancement $2 argument to my python script. How would I do that?

2
  • 7
    is it a copy/paste typo or are you not using snr inside for loop ?
    – Archemar
    Jan 19, 2020 at 13:13
  • 5
    Shouldn't it be something like --snr "$snr"?
    – Barmar
    Jan 20, 2020 at 2:21

5 Answers 5

33

Modifying your original script:

#!/bin/sh
echo "Noise $1"
echo "Enhancement $2"
for snr in 0 5 10 15 20 25
do
  python evaluate.py --noise "$1" --snr "$snr" --iterations 1250 ${2:+--enhancement "$2"}
done

The standard parameter expansion ${var:+word} will expand to word if the variable var is set and not empty. In the code above, we use it to add --enhancement "$2" to the command if $2 is available and not empty.

I've also taken the liberty to assume that what you are giving to --snr as an option-argument should be the loop variable's value.


My personal touch on the code (mostly just using printf rather than echo, avoiding long lines, and giving the code a bit more air):

#!/bin/sh

printf 'Noise %s\n' "$1"
printf 'Enhancement %s\n' "$2"

for snr in 0 5 10 15 20 25; do
    python evaluate.py \
        --noise "$1" \
        --snr "$snr" \
        --iterations 1250 \
        ${2:+--enhancement "$2"}
done

As mosvy points out in comments below: If your /bin/sh happens to be the dash shell, or some other shell that does not properly reset IFS as a new shell session starts (this is required by POSIX), and if you have, for one reason or other, exported IFS and given it a non-default value, then you may want to use unset IFS at the top of the above script.

Do that whenever you have fixed all other issues that exporting IFS doubtlessly have raised (don't export IFS).

6
  • You should also make sure IFS is at its default value. But a gotcha of this is that it will treat an empty $2 argument just like no argument at all. And ${2+... "$2"} instead of ${2:+... "$2"} will not cope with that either (as I had foolishly assumed minutes ago)
    – user313992
    Jan 19, 2020 at 13:34
  • @mosvy Thanks. The shell will set IFS to the correct value, and the script is not changing it. As for the issue with $2, I'm assuming that an empty value is not something the user will want to pass on with the --enhancement long option.
    – Kusalananda
    Jan 19, 2020 at 13:57
  • "The shell will set IFS to the correct value" will it? IFS=, sh -c 'v=a,b; echo $v' will print a b with the default /bin/sh on debian. As a matter of practicality, the OP could ... do the whole thing in python instead of bash.
    – user313992
    Jan 19, 2020 at 15:13
  • @mosvy I'd consider that a bug in dash as POSIX actually says "The shell shall set IFS to <space> <tab> <newline> when it is invoked."
    – Kusalananda
    Jan 19, 2020 at 15:21
  • It's fixed in newer versions of dash, but that doesn't matter: it will take a while until eg. debian 10 gets eol'ed. And people copy shell snippets around, without regard whether they're intended to work as a standalone script. As it stands, your answer is broken: it should both explicitly unset IFS and also mention the empty $2 gotcha.
    – user313992
    Jan 19, 2020 at 17:26
14

On a minimal POSIX shell, that does not support any arrays, you can just manipulate the positional argument list with set command. Just before the for loop you can do

if [ -z "$2" ]; then
    set -- --noise "$1" --snr 25 --iterations 1250
else
    set -- --noise "$1" --snr 25 --iterations 1250 --enhancement "$2"
fi

and now run your for loop as

for snr in 0 5 10 15 20 25; do
    python evaluate.py "$@"
done

If your shell supports array bourne again shell bash, zsh or other, you can just use an array

argv=(--noise "$1" --snr 25 --iterations 1250)

if [ ! -z "$2" ]; then
    argv+=( --enhancement "$2" )
fi

and now call the script as

python evaluate.py "${argv[@]}"

As pointed in the comments, you are not using the actual value of snr value from the for loop. Use the value as $snr where applicable in the script arguments, in which case you might need to move the logic of constructing the argument list inside the loop.

0
2

You can do it in a single line by using command substitution

  python evaluate.py --noise $1 --snr 25 --iterations 1250 $(test -n "$2" && echo --enhancement "$2")
1
  • 1
    This fails if the value of $2 can be subject to word-splitting/pathname expansion after the command substation completes.
    – chepner
    Jan 21, 2020 at 14:01
2

Build the argument in a variable based on the input:

#!/bin/sh
echo "Noise $1"
echo "Enhancement $2"

if [ $# -eq 1 ]
then
   enhance=""
else
   enhance="--enhancement $2"
fi

for snr in 0 5 10 15 20 25
do
  python evaluate.py --noise $1 --snr "$snr" --iterations 1250 $enhance
done

(Also includes the correction of the loop variable in the body of the loop)

6
  • 1
    that's bad; passing a second argument like * or 3000 foo will break the script.
    – user313992
    Jan 19, 2020 at 13:18
  • No, the script would function the same as the OP's script in those cases. Jan 19, 2020 at 13:23
  • 2
    ie it will malfunction in the same way
    – user313992
    Jan 19, 2020 at 13:25
  • ie it doesn't receive such values for the second argument. Jan 19, 2020 at 13:26
  • Thanks for the catch. Jan 19, 2020 at 13:28
0

The only way to make a variable expansion disappear is by having the expansion expand to an empty value when that expansion is unquoted.

$ unset a; b=''; c=set
$ printf '<%s> ' unquoted $a $b quoted "$a" "$b" val $c "$c"; echo
<unquoted> <quoted> <> <> <val> <s> <t> <set>

Both unset and empty values of a variable (a and b) disappear (as arguments) when unquoted. Both are retained when quoted.

Furthermore, even spaces (or any character) will disappear if included in IFS.

$ IFS=' '
$ unset a; b=' '; c='  set  '
$ printf '<%s> ' unquoted $a $b quoted "$a" "$b" val $c "$c"; echo
<unquoted> <quoted> <> < > <val> <set> <  set  >

In fact, an IFS that contains characters in the value to expand (unquoted) will separate arguments.

$ IFS='e '
$ $ printf '<%s> ' unquoted $a $b quoted "$a" "$b" val $c "$c"; echo
<unquoted> <quoted> <> < > <val> <s> <t> <  set  >

Note the two arguments <s> and <t> which resulted from expanding the value set when IFS contained e.

So, we need an unquoted expansion of unset or empty values which becomes a quoted expansion of the value when there is a value to expand:

${var:+"$var"}

Description:

${             # starts an un-quoted variable expansion
  var          # name of variable to expand
     :         # also replace if var is null
      +        # use what follows if is not unset (nor null)
       "$var"  # a quoted variable expansion.
             } # end of expansion.

# In short: expand "$var" if it has a value, $var otherwise.

We can then use $1 value to either disappear it or expand it

echo ${1:+"--noise"} ${1:+"$1"}

The line above will either print two separate arguments (not affected by IFS) if $1 has some (not null) value or nothing if $1 is empty or unset.

The script will become:

#!/bin/sh
echo "Noise $1"
echo "Enhancement $2"

for    snr in 0 5 10 15 20 25
do     python evaluate.py \
         ${1:+"--noise"} ${1:+"$1"} \
         --snr "$snr" \
         --iterations 1250 \
         ${2:+"--enhancement"} ${2:+"$2"}
done

This solution is not affected by the value of IFS

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .