سؤال

I am accustomed to testing that a variable has a non-null value (or give a message and bail) using a line like this:

test $variable || (echo "Value of \$variable cannot be null."; exit 1)

I'm quite new to using functions in my scripts, but I've got a case where I need to be sure a non-null value is passed or bail out of the function. However the command grouping for the "or" case is not working the same way inside the function. I wrote this little test script to demonstrate:

 $ cat -n bubu.sh
 1  #!/bin/bash
 2
 3  read -p "give a value for variable \$foo: " -e -i "bar" foo
 4
 5  function firstfunc {
 6          test $1 || (echo "no value"; return 1)
 7          echo it looks like \"$1\" is  the first value of \$foo
 8          return 0
 9  }
10
11  function secondfunc {
12          test $1 || return 1
13          echo it looks like \"$1\" is the second value of \$foo
14          return 0
15  }
16  echo "first function:"
17  firstfunc $foo
18  echo returned $?
19
20  echo "second function:"
21  secondfunc $foo
22  echo returned $?

And the output in the case where the variable has a value is like this:

$ ./bubu.sh
give a value for variable $foo: bar
first function:
it looks like "bar" is the first value of $foo
returned 0
second function:
it looks like "bar" is the second value of $foo
returned 0

And the output in the case where there is not a value

$ ./bubu.sh
give a value for variable $foo:
first function:
no value
it looks like "" is the first value of $foo
returned 0
second function:
returned 1

In the first function when there is not a value I get the first command of the "or" command group, and "no value" echoes, but the return command is passed by and the rest of the function is executed, down to the return.

Why are the parens for command grouping behaving differently inside the function, or what else am I missing?

هل كانت مفيدة؟

المحلول

As mentioned by others, the parens create a subshell and the exit or return command instructs bash to exit from the subshell. You should use curly braces. Note that you can use bash parameter substitution to get the same effect in a simpler way. See below.

Your line test $variable || (echo "Value of \$variable cannot be null."; exit 1) should not work either even 'outside' of the context of a function. For instance:

$ cat t.sh 
#!/bin/bash

a="hello"
test $a || (echo "Value of \$variable cannot be null."; exit 1)
echo First test passed

a=
test $a || (echo "Value of \$variable cannot be null."; exit 1)
echo Second test passed

gives:

$ ./t.sh 
First test passed
Value of $variable cannot be null.
Second test passed
$ 

Where we see that the second echo is run even though it shoulnd't have. Using curly braces is the way to go.

$ cat t.sh 
#!/bin/bash

a="hello"
test $a || { echo "Value of \$variable cannot be null."; exit 1 ; }
echo First test passed

a=
test $a || { echo "Value of \$variable cannot be null."; exit 1 ; }
echo Second test passed
$ ./t.sh 
First test passed
Value of $variable cannot be null.
$

Note that you could use the bash ${parameter:?err_msg} construct for parameter substitution:

$ cat t.sh 
#!/bin/bash

a="hello"
: ${a:?'a is null or undefined! Exiting..'}
echo First test passed

a=
: ${a:?'a is null or undefined! Exiting..'}
echo Second test passed

$ ./t.sh 
First test passed
./t.sh: line 9: a: a is null or undefined! Exiting..
$

نصائح أخرى

Inside the parentheses, the return and the exit cause the subshell created by the parentheses to exit, not the shell which executed the ... || ... list. You want to use {...} instead.

function firstfunc {
    test $1 || { echo "no value"; return 1; }
    echo it looks like \"$1\" is  the first value of \$foo
    return 0
}

Note that the braces are a little more finicky than parentheses: the final command must be followed by a semi-colon, and you must make sure the braces are separated by spaces from the surrounding characters.

The () around echo "no value"; return 1 will run those commands in a subshell. The return will just return you out of the subshell, and not all the way out of the function. You can use {} instead to achieve command grouping without a subshell. Also make sure the return is followed by a ; before the closing }:

 6          test $1 || { echo "no value"; return 1; }

See the Bash manual command grouping section for more info.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top