What I'm looking for is a compact line of code that uses the value of an expression twice in one nested statement
for an assignment to a variable and
a condition test of the variable's new value against another value
The questions are:
Is this compact form generally possible in bash?
If it is, do I have to fear negative side effects (unexpected results)?
You can certainly make the assignment before:
$ local bus_connection="$(lsblk -dn -o TRAN /dev/sdd)"
$ test "$bus_connection" = "usb" || { echo "not usb"; exit 1 }
$ echo "$bus_connection"
usb
--> Works!
If variable 'bus_connection' is only needed in case of success you also can do:
$ test "$(lsblk -dn -o TRAN /dev/$DEVICE)" = "usb" \
&& local bus_connection="usb" \
|| { echo "not usb"; exit 1 }
$ echo "$bus_connection"
usb
--> Also works! (Don't use 'local' on the command line!)
In C(++) and some other compiled high level languages you can do it more compact (let's suppose a c function 'lsblk'):
if( ( bus_connection = lsblk( ... ) ) == "usb" ) ; else {printf("not usb"); exit 1;}
In bash I tried:
$ test $(bus_connection="$(lsblk -dn -o TRAN /dev/sdd)") = "usb" || echo "not usb"
bash: test: =: unary operator exspected.
not usb
--> Error message and wrong result!
I tested with an integer case and found:
$ sum=0
$ result=12
$ test $((sum=$sum+$result)) -gt 20 && echo "true"
$ test $((sum=$sum+$result)) -gt 20 && echo "true"
true
--> Works! Negative side effects?
Analog:
$ test $((bus_connection="$(lsblk -dn -o TRAN /dev/sdd)")) = "usb" || echo "not usb"
not usb
--> No error message but wrong result!
$ echo "$bus_connection"
0
--> Ah, obviously bash treats variable 'bus_connection' as of type integer now.
Any suggestions for strings?
Is this compact form generally possible in bash
No, it is not, at least not without a custom code to handle it. Command substitution is run in a subshell - changes will not be visible in parent shell. Arithmetic expansion does only arithmetic operations - all strings are converted to numbers (as if by atoi()/strtol()
), if no digits are scanned they are converted to 0.
The usual way is just to assign the variable with a &&
followed by test - that allows to check the exit status of the command and grab the result.
if ! {
bus_connection="$(lsblk -dn -o TRAN /dev/sdd)" &&
test "$bus_connection" = "usb"
}; then
echo "not usb"
exit 1
fi
or just ignore the exit status of lsblk
:
if
bus_connection="$(lsblk -dn -o TRAN /dev/sdd)"
! test "$bus_connection" = "usb"
then
echo "not usb"
exit 1
fi
Still you could write your own function that uses bash namereference to assign the first argument to the second and return with test
s exit status:
test_and_assign() {
declare -n _test_and_assign_ref=$1
shift
_test_and_assign_ref="$1"
test "$@"
}
$ test_and_assign bus_connection "$(echo 123)" = "usb"
$ echo "$? $bus_connection"
1 123
$ test_and_assign bus_connection "$(echo usb)" = "usb"
$ echo "$? $bus_connection"
0 usb
Note that here, as in your first code snippet with local bus_connection=$(...)
, the exit status of the command substitution is ignored.