0

I'm writing the following script and I found the argument is not passed to a function called. However, when in an individual shell I can archive this.

usage() { if [ -n "$error_mess" ]; then echo -e "$error_mess"; fi; echo -e "usage stamens too long to present" 1>&2; exit 1; }

while getopts ":f:" flag
do
        case "${flag}" in
                f) if [ -n "${OPTARG}" ] ; then file=${OPTARG} ; else error_mess='\033[1m\033[5mError: file cannot be empty\033[0m' | usage; fi;;
                *) usage;;
        esac
done

I tried to put error_mess section after usage when calling inside the switch statement, but this error message is still not printing out.

When tried directly in bash it is working (only when the first line is run). The code entered directly to shell was:

# To check syntax error
error_mess='\033[1m\033[5mError: file cannot be empty\033[0m' ; if [ -n "$error_mess" ]; then echo -e "$error_mess"; fi

# To check if it can be passed inside a function
usage() { if [ -n "$error_mess" ]; then echo -e "$error_mess"; fi; echo -e "usage statement" 1>&2; exit 1; } ; error_mess='\033[1m\033[5mError: file cannot be empty\033[0m' | usage

# To check if it can be nested in if statement
a=0; if [ $a = 1 ] ; then echo "$a" ; else error_mess='\033[1m\033[5mError: file cannot be empty\033[0m' | usage ; fi

If I'm not doing it right, how should I correct it? I have other use case that do not have an error_mess or is having a different error_mess.

  • Have you tried `error_mess='…' usage`? – Biffen Nov 14 '22 at 17:43
  • 2
    (OT: What’s the point of so many semicolons and so long and few lines? It’s really unpleasant to read.) – Biffen Nov 14 '22 at 17:44
  • It looks like you're trying to pipe from the assignment to the function, but that's not what pipes are for at all. A pipe takes the output of the first command (i.e. what it prints), and pipes it to the second command so the second command can read it. But assigments don't print anything (they set variables), and your `usage` function doesn't read input (it just uses a variable). Also, the pipe makes the commands run in different processes (at the same time), so the variable is getting set in a different process from where you're trying to use it. – Gordon Davisson Nov 14 '22 at 18:13
  • 1
    `var=value | usage` should just be `var=value usage` -- no `|` or other separator inbetween. But that's not _passing an argument_ at all; an argument would be `usage value` – Charles Duffy Nov 14 '22 at 18:29
  • 1
    Also, `echo -e ...anything...` is a bad idea; use `printf '%b\n' ...anything...` instead. See the APPLICATION USAGE and RATIONALE sections of [the POSIX standard for `echo`](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html), and/or the excellent answer by Stephane on [Why is printf better than echo?](https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo) over on [unix.se]. – Charles Duffy Nov 14 '22 at 18:31
  • @CharlesDuffy I tried to use `usage error_msg='value'` but it is not working, that's why pipe came to my mind. – jimmymcheung Nov 14 '22 at 22:31
  • @Biffen Yes but because it is not working I gave the `|` a try. But still... I know `|` is for output, For the moment I was coding I really thought `var=value` will be an output of itself. – jimmymcheung Nov 14 '22 at 23:08
  • @jimmymcheung, right, because the ordering is `error_msg='value' usage`, with the assignment **before** the function name, if you want to set the variable `error_msg` in the function's environment (instead of passing `error_msg='value'` as an argument, which is what `usage error_msg='value'` does). Note that passing arguments only does something useful if the thing you're passing them to _looks at_ its arguments; if the function being started doesn't try to process arguments, they don't do anything by magic. – Charles Duffy Nov 14 '22 at 23:12
  • If you use `usage 'value'`, then inside of the function `usage` you would want to set `local error_msg="$1"` to set a local variable from the argument being passed; the linked duplicate describes this. – Charles Duffy Nov 14 '22 at 23:13
  • @CharlesDuffy I don't want to have ambiguous argument being passed into `usage`, that's why I specifically specify the variable name of the argument outside of the function. But I'm afraid with `$1` anything either due to careless coding or unwanted script passed in script argument can be passed, leading unwanted result. Wouldn't `$1` gets whatever is passed to the function? – jimmymcheung Nov 14 '22 at 23:29
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/249604/discussion-between-charles-duffy-and-jimmymcheung). – Charles Duffy Nov 14 '22 at 23:30

1 Answers1

3

When you do cmd1 | cmd2 in bash, each command is executed in a separate subshell.

When one of the commands is var=value, then that variables only exists in that subshell.

You'll want to remove the pipe

if [ -n "$OPTARG" ]; then
    file=$OPTARG
else
    error_mess='\033[1m\033[5mError: file cannot be empty\033[0m' usage
fi

Think about passing the error message as an argument to the usage function. Then if you accidentally set the error_mess in one place but call usage in another place, you won't get a spurious error message.


If you're willing to give up the fancy error output:

while getopts "f:" flag     # remove leading colon
do
    case "${flag}" in
        f) file=${OPTARG:?Error: file cannot be empty} ;;
           # ...........^^
        *) usage;;
    esac
done

Then:

$ bash prog.sh -f
prog.sh: option requires an argument -- f
usage ...

$ bash prog.sh -f ""
prog.sh: line 5: OPTARG: Error: file cannot be empty

From the manual

${parameter:?word}

If parameter is null or unset, the expansion of word (or a message to that effect if word is not present) is written to the standard error and the shell, if it is not interactive, exits. Otherwise, the value of parameter is substituted.


To conclude, what you probably want is this:

if [ -n "$OPTARG" ]; then
    file=$OPTARG
else
    usage 'Error: file cannot be empty'
fi

And then usage looks like:

usage() {
    {
        [[ "$1" ]] && printf '\033[1m\033[5m%s\033[0m\n' "$1"
        echo -e "usage stamens too long to present"
    } 1>&2
    exit 1
}
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • So I tried to remove the pipe and it works in terminal, however, it is still not appearing in the script output. I don't need fancy formatting, but I thought it would be helpful to also print usage (as I saw many command like git does so) – jimmymcheung Nov 14 '22 at 22:29
  • I kept leading `:` because I want to have `-h` option or any other illegal option to display usage, meanwhile supplying `-h` itself wouldn't produce warning of illegal option. Is that totally unnecessary? – jimmymcheung Nov 14 '22 at 23:16
  • No, I usually use the leading colon too. I was just pointing out an alternative. – glenn jackman Nov 14 '22 at 23:58