bash if -a vs -e option - bash

Both about -a and -e options in Bash documentation is said:
-a file
True if file exists.
-e file
True if file exists.
Trying to get what the difference is I ran the following script:
resin_dir=/Test/Resin_wheleph/Results
if [ -e ${resin_dir} ] ; then
echo "-e ";
fi
if [ ! -e ${resin_dir} ] ; then
echo "! -e";
fi
if [ -a ${resin_dir} ] ; then
echo "-a";
fi
if [ ! -a ${resin_dir} ] ; then
echo "! -a";
fi
/Test/Resin_wheleph/Results exists and is a directory. And this is what I get:
-e
-a
! -a
which seems to be a little strange (notice -a and ! -a). But when I use double brackets (e. g. if [[ -e ${resin_dir} ]]) in the similar script it gives reasonable output:
-e
-a
So:
What is a difference between -a and -e options?
Why -a produces a strange result when used inside single brackets?

I researched, and this is quite hairy:
-a is deprecated, thus isn't listed in the manpage for /usr/bin/test anymore, but still in the one for bash. Use -e . For single '[', the bash builtin behaves the same as the test bash builtin, which behaves the same as /usr/bin/[ and /usr/bin/test (the one is a symlink to the other). Note the effect of -a depends on its position: If it's at the start, it means file exists. If it's in the middle of two expressions, it means logical and.
[ ! -a /path ] && echo exists doesn't work, as the bash manual points out that -a is considered a binary operator there, and so the above isn't parsed as a negate -a .. but as a if '!' and '/path' is true (non-empty). Thus, your script always outputs "-a" (which actually tests for files), and "! -a" which actually is a binary and here.
For [[, -a isn't used as a binary and anymore (&& is used there), so its unique purpose is to check for a file there (although being deprecated). So, negation actually does what you expect.

The '-a' option to the test operator has one meaning as a unary operator and another as a binary operator. As a binary operator, it is the 'and' connective (and '-o' is the 'or' connective). As a unary operator, it apparently tests for a file's existence.
The autoconf system advises you to avoid using '-a' because it causes confusion; now I see why. Indeed, in portable shell programming, it is best to combine the conditions with '&&' or '||'.
I think #litb is on the right track. When you have '! -a ${resin_dir}', Bash may be interpreting it as "is the string '!' non-empty and is the string in '${resin_dir}' non-empty, to which the answer is yes. The Korn shell has a different view on this, and the Bourne shell yet another view - so stay away from '-a'.
On Solaris 10:
$ bash -c 'x=""; if [ ! -a "$x" ] ; then echo OK ; else echo Bad; fi'
Bad
$ ksh -c 'x=""; if [ ! -a "$x" ] ; then echo OK ; else echo Bad; fi'
OK
$ sh -c 'x=""; if [ ! -a "$x" ] ; then echo OK ; else echo Bad; fi'
sh: test: argument expected
$

The double bracket [[ exp ]] is a bash builtin. In bash -a and -e are the same, probably for some backwards compatibility.
The single bracket [ exp ] is an alias for the external command "test". In "test", -a is a logical AND. Although [ nothing AND $STRING ] looks like it should be false, test has some syntax quirks, which is why I recommend using the bash builtin [[ exp ]], which tends to be more sane.
Note:
bash really does call /bin/[ when you use "[".
$ [ $UNASIGNED_VAR == "bar" ]
bash: [: ==: unary operator expected
the error shows bash called [. An strace also shows "execve("/usr/bin/[", ..."

Related

Bash script that checks for parts of current folderpath

Clean and simple: how do I check with bash for certain parts of the folder I'm currently in?
#!/usr/bin/sh
CURRENTFOLDER=$(pwd)
echo "${CURRENTFOLDER}"
CHECKFOLDER="/home/*/domains/*/public_html"
if [ $CURRENTFOLDER ! $CHECKFOLDER ]
then
echo "Current folder is not /home/user/domains/domain.com/public_html"
exit
fi
User and domain are variable, I don't need to know them for this checkup, just the 3 pre-defined folders in the variable CHECKFOLDER
There's a problem with this approach.
For example in bash the following expression evaluates to true:
[[ /www/user/domains/local/public_html == /www/*/public_html ]]
It is more accurate to use a bash regex:
[[ /www/user/domains/local/public_html =~ ^/www/[^/]+/public_html$ ]]
So your code would become:
#!/bin/bash
current_folder=$PWD
check_folder='^/home/[^/]+/domains/[^/]+/public_html$'
if ! [[ $current_folder =~ $check_folder ]]
then
echo "Current folder is not /home/user/domains/domain.com/public_html"
exit
fi
BTW, the shebang needs to be a bash, not sh. And it's kind of dangerous to capitalize your variables.
Try this (almost) Shellcheck-clean code:
#! /usr/bin/sh
curr_phpath=''
for phpath in /home/*/domains/*/public_html/; do
if [ "$phpath" -ef . ]; then
curr_phpath=$phpath
break
fi
done
if [ -z "$curr_phpath" ]; then
echo "Current folder is not /home/user/domains/domain.com/public_html" >&2
exit 1
fi
Because of aliasing mechanisms (e.g. symbolic links, bind mounts) it is very difficult in general to determine if two paths reference the same file or directory by comparing them textually. See How to check if two paths are equal in Bash? for more information. This solution uses a more reliable mechanism to determine if the current directory is one of the valid ones.
Since the shebang line references sh instead of bash, the code avoids Bashisms. It's been tested with both bash and dash (probably the most common non-Bash sh).
See Correct Bash and shell script variable capitalization for an explanation of why the code does not use ALL_UPPERCASE variable names.
The [ "$phpath" -ef . ] test is true if the .../public_html path being checked is the same directory as the current directory. The -ef operator is not in POSIX so it is not guaranteed to be supported by an sh shell, and Shellcheck (correctly) warns about it. However, it is supported in both bash and dash, and sh is usually one of those (on Linux at least).
You can save a step just by changing to the directory instead of checking.
Check your glob matches only one file first.
Then, cd to check it's a dir.
#! /bin/bash
IFS="$(printf '\n\t')"
files=( $(compgen -G '/home/*/domains/*/public_html') )
if [[ "${#files[#]}" != 1 ]]
then
printf 'Multiple matches\n' >&2
exit 1
fi
if ! cd "${files[0]}"
then
printf 'Cannot chdir\n'
exit 1
fi

How to check if the output of a command contains a string and then run a command if the string exists

Example
if "darwin" in $MACHTYPE;
then
echo "whoa it's a mac!"
fi
And the output should be
whoa it's a mac, if darwin is found in the output of $MACHTYPE
Please guide me!
Provided you're using bash, you could use the =~ operator:
if [[ "$MACHTYPE" =~ "darwin" ]];
then
echo "whoa it's a mac!"
fi
From the bash man page:
An additional binary operator, =~, is available, with the same precedence as == and !=. When it is used, the string to the right of the operator is considered an extended regular expression and matched accordingly (as in regex(3)).
If you don't have a version of bash which supports regular expressions then you can use globbing:
if [[ $MACHTYPE = *darwin* ]]
then
echo "whoa it's a mac!"
fi
Note that you must use [[, not [.
Other shells like sh might support [[ but that is not guaranteed by the standard.
You could evaluate your command directly, for example:
if uname -a | grep -i "darwin" > /dev/null; then
echo "it is a mac"
fi
In this case, grep will exit 0 if found a value and output will be redirected to /dev/null if try then you can call your command, in this case: echo "it is a mac"
The code below can get the output of your command on the cmd, then check if there has the specific word.
command="command here"
if[ `echo $command | grep -c "\"darwin\""` -gt 0 ]; then
Do anything you want here
fi

Bash wildcard in a makefile not working

I have this makefile:
SHELL := /bin/bash -f
working :
if [ -d ffprob_runfail ]; then echo "gotcha" ;fi
error :
if [ -d ffprob_* ]; then echo "gotcha" ;fi
Executing 'make working' in a folder where the subdirectory 'ffprob_runfail' exists echoes:
if [ -d ffprob_runfail ]; then echo "gotcha" ;fi
gotcha
Executing 'make error' in the same folder echoes:
if [ -d ffprob_* ]; then echo "gotcha" ;fi
I am not sure where this 'surprising' behavior comes from - either miscoding in make or bash syntax. I tried escaping * but did not work. Might be an issue with the syntax of [ ] bash operator? (I am quite new to bash, after 20 years of csh pain...)
Any hint appreciated.
POST EDIT:
Not only the -f option disables globbing (thanks #choroba), but also the -d operator in bash is unary, and cannot used safely with globbing, i.e. refer to Bash Shell Script: confirm the existance of one or more directories.
So this looks to be the right way (continuation of the previous makefile...):
right :
for item in ffprob_* ; do if [ -d "$$item" ] ; \
then echo "gotcha $$item";fi;done
The -f option for bash means the same as the -f option to set, namely
-f Disable file name generation (globbing).
With globbing disabled, wildcards aren't expanded.
So, why do you set the shell to bash -f? Remove the -f.

Shell scripting - if statement difference

This a question of an exercise:
What is the difference between the two "if" instructions?
#!/bin/bash
rm tmp
echo -n > tmp
for f in $*
do
if test ! -f $f
then
echo $f does not exist as a file
continue
fi
rm $f
if [ ! -f $f ]
then
echo $f has been deleted successfully
fi
ls $f >> tmp
done
x='cat tmp | grep -c ^.*$'
echo result: $x
The square brackets are a synonym for the test command, instead of if test ! -f $f we can use if [ ! -f $f ]. Note: test is a command which takes expression and test or evaluates.
No difference. test and [ are builtins in most (all?; definitely in dash, bash, yash, ksh, zsh, fish) shells now:
$ type [
[ is a shell builtin
$ type test
test is a shell builtin
There's also executable versions of them:
$ which [
/usr/bin/[
$ which test
/usr/bin/test
Unlike cd, test (or [) doesn't need to be a builtin (at least not for the common options -- some shells' extensions require it to be a builtin), but the fork+exec overhead of an external executable is too much for the little things that test tests.

bash: "logical and" checks if file exists? [duplicate]

Both about -a and -e options in Bash documentation is said:
-a file
True if file exists.
-e file
True if file exists.
Trying to get what the difference is I ran the following script:
resin_dir=/Test/Resin_wheleph/Results
if [ -e ${resin_dir} ] ; then
echo "-e ";
fi
if [ ! -e ${resin_dir} ] ; then
echo "! -e";
fi
if [ -a ${resin_dir} ] ; then
echo "-a";
fi
if [ ! -a ${resin_dir} ] ; then
echo "! -a";
fi
/Test/Resin_wheleph/Results exists and is a directory. And this is what I get:
-e
-a
! -a
which seems to be a little strange (notice -a and ! -a). But when I use double brackets (e. g. if [[ -e ${resin_dir} ]]) in the similar script it gives reasonable output:
-e
-a
So:
What is a difference between -a and -e options?
Why -a produces a strange result when used inside single brackets?
I researched, and this is quite hairy:
-a is deprecated, thus isn't listed in the manpage for /usr/bin/test anymore, but still in the one for bash. Use -e . For single '[', the bash builtin behaves the same as the test bash builtin, which behaves the same as /usr/bin/[ and /usr/bin/test (the one is a symlink to the other). Note the effect of -a depends on its position: If it's at the start, it means file exists. If it's in the middle of two expressions, it means logical and.
[ ! -a /path ] && echo exists doesn't work, as the bash manual points out that -a is considered a binary operator there, and so the above isn't parsed as a negate -a .. but as a if '!' and '/path' is true (non-empty). Thus, your script always outputs "-a" (which actually tests for files), and "! -a" which actually is a binary and here.
For [[, -a isn't used as a binary and anymore (&& is used there), so its unique purpose is to check for a file there (although being deprecated). So, negation actually does what you expect.
The '-a' option to the test operator has one meaning as a unary operator and another as a binary operator. As a binary operator, it is the 'and' connective (and '-o' is the 'or' connective). As a unary operator, it apparently tests for a file's existence.
The autoconf system advises you to avoid using '-a' because it causes confusion; now I see why. Indeed, in portable shell programming, it is best to combine the conditions with '&&' or '||'.
I think #litb is on the right track. When you have '! -a ${resin_dir}', Bash may be interpreting it as "is the string '!' non-empty and is the string in '${resin_dir}' non-empty, to which the answer is yes. The Korn shell has a different view on this, and the Bourne shell yet another view - so stay away from '-a'.
On Solaris 10:
$ bash -c 'x=""; if [ ! -a "$x" ] ; then echo OK ; else echo Bad; fi'
Bad
$ ksh -c 'x=""; if [ ! -a "$x" ] ; then echo OK ; else echo Bad; fi'
OK
$ sh -c 'x=""; if [ ! -a "$x" ] ; then echo OK ; else echo Bad; fi'
sh: test: argument expected
$
The double bracket [[ exp ]] is a bash builtin. In bash -a and -e are the same, probably for some backwards compatibility.
The single bracket [ exp ] is an alias for the external command "test". In "test", -a is a logical AND. Although [ nothing AND $STRING ] looks like it should be false, test has some syntax quirks, which is why I recommend using the bash builtin [[ exp ]], which tends to be more sane.
Note:
bash really does call /bin/[ when you use "[".
$ [ $UNASIGNED_VAR == "bar" ]
bash: [: ==: unary operator expected
the error shows bash called [. An strace also shows "execve("/usr/bin/[", ..."

Resources