Bash: Safe to remove all occurrences of -n inside brackets? [duplicate] - bash

This question already has answers here:
Bash operators: "!" vs "-z"
(2 answers)
Closed 3 years ago.
I recently found this "bug" in my bash script;
if [ "$var" ]; then
echo "Condition is true"
fi
where what I meant was to check if $var is non-empty, i.e. [ -n "$var" ]. As it happens, the code seems to work perfectly fine without the -n. Similarly, I find that I can replace [ -z "$var" ] with [ ! "$var" ].
I tend to like this implicit falseyness (truthiness) based on the (non-)emptyness of a variable in other languages, and so I might adopt this pattern in bash as well. Are there any danger to doing so, i.e. edge cases where the two set of syntaxes are inequivalent?

So we substitute:
-n "$var" -> "$var"
-z "$var" -> ! "$var"
That looks ok and some people do that. There are corner cases where the removal will be harmful and result in syntax errors. These corner cases specially include times, where var is equal to a valid test argument. Ex. var=-n or var="!" etc.
Ex:
$ v1="-n" v2=""; [ -n "$v1" -o -z "$v2" ]; echo $?
0
but
$ v1="-n" v2=""; [ "$v1" -o ! "$v2" ]; echo $?
bash: [: too many arguments
2
That said, I couldn't find a way on my system (bash 5.0.0) to break it without using -o and -a. And anyway, the test man page advises to use && and || instead of -a and -o.
But in POSIX specification test we can find the following application note:
The two commands:
test "$1"
test ! "$1"
could not be used reliably on some historical systems. Unexpected
results would occur if such a string expression were used and $1
expanded to '!', '(', or a known unary primary. Better constructs are:
test -n "$1"
test -z "$1"
So I think as long as you don't use "some historical systems", you are safe. But is it worth the risk? You have to answer yourself.
That said, subjective: I value maintainability and readability much more then saving to type 3 characters and find -n and -z more readable. The intent with -n is clear - test if the string has -nonzero length. The intent with -z is also clear - test if the string has -zero length. I find it confusing to others to write [ "$var" ] and [ ! "$var" ]. At first it would look like $var has some special meaning inside the [ ].
I recently found this "bug" in my bash script
It's not a bug, it's a feature!

First of all, you make use of single brackets. This implies that you are using the test command and not the Bash-builtin function. From the manual :
test EXPRESSION or [ EXPRESSION ]: this exits with the status returned by EXPRESSION
-n STRING: the length of STRING is nonzero.
STRING: equivalent to -n STRING
source: man test
This should answer your question.
Furthermore:
! EXPRESSION: test returns true of EXPRESSION is false
-z STRING: test returns true if the length of STRING is zero.
Which implies that [ ! STRING ] is equivalent to [ -z STRING ].

Related

Bash ping command in if statement with square brackets does not work [duplicate]

Usually, I use square brackets in the if statement:
if [ "$name" = 'Bob' ]; then ...
But, when I check if grep succeeded I don't use the square brackets:
if grep -q "$text" $file ; then ...
When are the square brackets necessary in the if statement?
The square brackets are a synonym for the test command. An if statement checks the exit status of a command in order to decide which branch to take. grep -q "$text" is a command, but "$name" = 'Bob' is not--it's just an expression. test is a command, which takes an expression and evaluates it:
if test "$name" = 'Bob'; then ...
Since square brackets are a synonym for the test command, you can then rewrite it as your original statement:
if [ "$name" = 'Bob' ]; then ...
[ is actually a command, equivalent (almost, see below) to the test command. It's not part of the shell syntax. (Both [ and test, depending on the shell, are often built-in commands as well, but that doesn't affect their behavior, except perhaps for performance.)
An if statement executes a command and executes the then part if the command succeeds, or the else part (if any) if it fails. (A command succeeds if it exits with a status ($?) of 0, fails if it exits with a non-zero status.)
In
if [ "$name" = 'Bob' ]; then ...
the command is
[ "$name" = 'Bob' ]
(You could execute that same command directly, without the if.)
In
if grep -q "$text" $file ; then ...
the command is
grep -q "$text" $file
man [ or man test for more information.
FOOTNOTE: Well, the [ command is almost equivalent to the test command. The difference is that [ requires ] as its last argument, and test does not -- and in fact doesn't allow it (more precisely, test doesn't treat a ] argument specially; for example it could be a valid file name). (It didn't have to be implemented that way, but a [ without a matching ] would have made a lot of people very very nervous.)
The best way to think of the [ ... ] syntax, is to consider [ to be a program - which it is!
Check this out:
~ $ ls /usr/bin/\[
/usr/bin/[
on the other hand, you're probably not using that version of it since bash also provides [ as a shell built-in.
Anyway, to answer your question: What if does is run the command you give it and see it the return value is 0 or not. You use [ to do other, more interesting comparisons such as string comparisons. See man [ and man bash.

Command online argument in shell script

Hi I have written small shell script, I am not able to understand the behavior of that script. can any one help me to understand that script.
Script:
#!/bin/bash
if [ -z $1 ]
then
echo "fail"
else
echo "success"
fi
While executing the script .
./test.sh one
It exuting the else statement instead of main statement , even though its passing the argument.
can any one explain me this behavior to understand
The -z test in bash is checking if a string is an empty (zero length) value.
Since you're passing an argument to the script $1 is not empty and therefore -z $1 evaluates to false, executing the else portion of your script.
Side note: Since you're working with strings I recommend you to quote variables as follows:
if [ -z "$1" ]; then
echo "String is empty / No argument given"
else
echo "String is not empty / Argument given"
fi
Edit:
As pointed out by user1934428 it's probably better to use [[ instead of [. This, among others, eliminates the need for quoting. See more differences here.
if [[ -z $1 ]]; then
...
However, be aware that this is a bash extension and won't work in sh scripts.

logical OR existence test for shell variables under bash

I need to perform some logic based on if shell_var_1 OR shell_var_2 is set under bash.
If I use #!/bin/sh, I could just use:
if [ "$shell_var_1" -ne "0" -o "$shell_var_2" -ne "0" ] ; then
logic
fi
This just uses "-o" from test (http://unixhelp.ed.ac.uk/CGI/man-cgi?test)
However, if I specify #!/bin/bash, and want to use the '-v' option to test the shell variable's existence (added from bash 4.2 onwards), how'd I go about doing the same?
Ref: https://tiswww.case.edu/php/chet/bash/bash.html
if [ -v shell_var_1 -o -v shell_var_2 ] ; then
logic
fi
^ Is this considered correct? Am I not mixing bash and the test operator?
[[ $shell_var_1 || $shell_var_2 ]]
...is a very succinct and idiomatic way to write this in bash, compatible back to ancient releases, assuming that you consider a variable set to an empty value not to exist.
If you want a portable alternative which treats variables set to zero-byte values as unset, then
[ -n "$shell_var_1" ] || [ -n "$shell_var_2" ]
...will serve.
If you want a portable alternative that treats variables set to empty values as existing, then
[ -n "${shell_var_1+set}" ] || [ -n "${shell_var_2+set}" ]
...will do this.
If you really want to use -v (thus, treating variables set to empty values as existing, but being needlessly incompatible with POSIX shells and older bash releases -- including those shipped with MacOS), then use [[ ]] -- which makes it unmistakably clear to readers that you're using bash-only syntax, and isn't prone to some obscure bugs which test or [ (even the bash built-in form of them) are prone to when used in more than 2-arg form:
[[ -v shell_var_1 || -v shell_var_2 ]]

What do the -n and -a options do in a bash if statement? [duplicate]

This question already has answers here:
Is there a list of 'if' switches anywhere?
(5 answers)
Closed 5 years ago.
What function do the -a and -n options perform in the following bash if statement?
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
REFNAME=$(basename $3)
else
Are -a and -n so called primaries?
Does -a file mean "True if file exists."?
-a Links two expressions together in an "and" or "&&" expression. This option is deprecated.
-n Checks if the length of a string is nonzero.
You could translate the test expression into the following pseudocode:
if ( ($1 has nonzero length) and
($2 has nonzero length) and
($3 has nonzero length) )
There are no checks in that expression for whether the file exists or doesn't exist, only whether the arguments have been supplied to the script.
The arguments -a and -n can be found in the manpage for test
man test
The operator [ ... ] is often used as shorthand for test ... and likely has identical functionality on your system.
Nitpicking
The switches -a and -n are not strictly part of a bash if statement in that the if command does not process these switches.
What are primaries?
I call them "switches", but the bash documentation that you linked to refers to the same thing as "primaries" (probably because this is a common term used when discussing parts of a boolean expression).
Background and docs
In sh scripts if is a command that takes a command as its argument, executes it and tests its return code. If the return code is 0 the block of code following then is executed up until the closing fi or (if supplied) the following else. If the return code was not 0 and an else statement was supplied then the block of code following else is executed up until the closing fi.
You can see this effect by passing if the command true or the command false, which are simple commands that do nothing and return 0 and non-0 respectively.
if true ; then echo true was true ; else echo true was false ; fi
if false ; then echo false was true ; else echo false was false ; fi
In the sample code you provided the command that you're passing to if is [, which is also sometimes known as test. It is this command which takes the switches you're asking about. In bash the test command will be a built-in command; try type [ to learn its type. For built-in commands help will show usage, so also run help [ to see documentation. Your system probably also has a /bin/[ and a /bin/test and if you man test you can see the manuals for those. Although the behavior of the built-in test may not be identical to the behavior documented in the man pages, which is likely more verbose than the simple description you'll get from help [, it will probably describe the behavior of the built-in [ command fairly accurately.
The behavior of -a and -n
Knowing that the command you're running is test we can consult help test or man test and read its usage. This will show that-n tests the following argument and evaluates to true if it is not an empty string.
In the documentation of test you will also see a the switch -e. This switch tests the following argument and evaluates to true if that argument is a file or directory that exists. More useful still is the -f switch which evaluates to true if the following argument exists and is a regular file (as opposed to a directory or a block device, or whatever).
The source of your confusion is probably that there can be two forms of -a: Unary and binary. When -a is used in a unary context, that is with one following argument but no preceding arguments, it treats its argument as a file and tests for its existence, just like the -e switch. However, when -a is used in a binary context, that is with one argument before it and one argument after it, it treats its arguments as other conditions and acts as a boolean AND operator.
In the interests of portability it is important to note that unary -a is a non-standard extension which won't be found in POSIX. It is available in bash and ksh, however, so usage is probably widespread.
Example
cd /tmp
if [ -a test-file ] ; then
echo 1: test-file exists
else
echo 1: test-file missing
fi
touch test-file
if [ -a test-file ] ; then
echo 2: test-file exists
else
echo 2: test-file missing
fi
var=somerthing
if [ -n "$var" -a -a test-file ] ; then
echo variable var is not empty and test-file exists
fi
rm -f test-file

When are square brackets required in a Bash if statement?

Usually, I use square brackets in the if statement:
if [ "$name" = 'Bob' ]; then ...
But, when I check if grep succeeded I don't use the square brackets:
if grep -q "$text" $file ; then ...
When are the square brackets necessary in the if statement?
The square brackets are a synonym for the test command. An if statement checks the exit status of a command in order to decide which branch to take. grep -q "$text" is a command, but "$name" = 'Bob' is not--it's just an expression. test is a command, which takes an expression and evaluates it:
if test "$name" = 'Bob'; then ...
Since square brackets are a synonym for the test command, you can then rewrite it as your original statement:
if [ "$name" = 'Bob' ]; then ...
[ is actually a command, equivalent (almost, see below) to the test command. It's not part of the shell syntax. (Both [ and test, depending on the shell, are often built-in commands as well, but that doesn't affect their behavior, except perhaps for performance.)
An if statement executes a command and executes the then part if the command succeeds, or the else part (if any) if it fails. (A command succeeds if it exits with a status ($?) of 0, fails if it exits with a non-zero status.)
In
if [ "$name" = 'Bob' ]; then ...
the command is
[ "$name" = 'Bob' ]
(You could execute that same command directly, without the if.)
In
if grep -q "$text" $file ; then ...
the command is
grep -q "$text" $file
man [ or man test for more information.
FOOTNOTE: Well, the [ command is almost equivalent to the test command. The difference is that [ requires ] as its last argument, and test does not -- and in fact doesn't allow it (more precisely, test doesn't treat a ] argument specially; for example it could be a valid file name). (It didn't have to be implemented that way, but a [ without a matching ] would have made a lot of people very very nervous.)
The best way to think of the [ ... ] syntax, is to consider [ to be a program - which it is!
Check this out:
~ $ ls /usr/bin/\[
/usr/bin/[
on the other hand, you're probably not using that version of it since bash also provides [ as a shell built-in.
Anyway, to answer your question: What if does is run the command you give it and see it the return value is 0 or not. You use [ to do other, more interesting comparisons such as string comparisons. See man [ and man bash.

Resources