Is there a list of all the if switches for use in Bash scripting? Sometimes I see someone using it and I wonder what the switch they're using actually does.
An example is the -z in this one. I know how to use it, but I don't know where it was derived from.
if [ -z "$BASH_VERSION" ]; then
echo -e "Error: this script requires the BASH shell!"
exit 1
fi
Any references, guides, posts, answers would be appreciated.
Look at the Bash man page (man bash). The options are specified in the CONDITIONAL EXPRESSIONS section:
CONDITIONAL EXPRESSIONS
Conditional expressions are used by the [[ compound command and the
test and [ builtin commands to test file attributes and perform string
and arithmetic comparisons. Expressions are formed from the following
unary or binary primaries. If any file argument to one of the pri-
maries is of the form /dev/fd/n, then file descriptor n is checked. If
the file argument to one of the primaries is one of /dev/stdin,
/dev/stdout, or /dev/stderr, file descriptor 0, 1, or 2, respectively,
is checked.
Unless otherwise specified, primaries that operate on files follow sym-
bolic links and operate on the target of the link, rather than the link
itself.
-a file
True if file exists.
... more options ...
It is also explained in the help:
$ help [
[: [ arg... ]
This is a synonym for the "test" builtin, but the last
argument must be a literal `]', to match the opening `['.
Yes. These are called conditional expressions and these are used by the [[ compound command and the test and [ builtin commands ([ is simply a synonym for test).
Read section 6.4 Bash Conditional Expressions of the Bash Reference Manual, which contains a list of all these switches and their usage.
The single square brackets ([ ... ]) is an synonym of the test command. If you look at the man page for test, you will see almost all (Bash might have a few extra not mentioned here) of the various if switches as you called them. All in one easy-to-find place.
If you use double square brackets ([[ ... ]]), you are using an extended Bash set of tests. These mainly have to do with regular expression matching, and glob matching (and extended glob matching if you have that set too). For that, you'll have to read that Bash man page.
You called them if switches, but that's not really correct. These are tests and really have nothing to do with the if command.
The if command merely executes the command you give it, and then if that command returns an exit code of 0, will run the if portion of the if statement. Otherwise, it will run the else portion (if that's present).
Let's look at this:
rm foo.test.txt # Hope this wasn't an important file
if ls foo.test.txt
> then
> echo "This file exists"
> else
> echo "I can't find it anywhere.."
> fi
ls: foo.test.txt: No such file or directory
I can't find it anywhere..
The if statement runs the ls foo.test.txt command and the ls command returns a non-zero because the file does not exist. This causes the if statement to execute the else clause.
Let's try that again...
touch foo.test.txt # Now this file exists.
if ls foo.test.txt # Same "if/else" statement as above
> then
> echo "This file exists"
> else
> echo "I can't find it anywhere.."
> fi
foo.test.txt
This file exists
Here, the ls command returned a 0 exit status (since the file exists and the file exists and can be stat'ed by the ls command.
Normally, you shouldn't use the ls command to test for a file. I merely used it here to show that the if statement executes the command, then executed the if or else clause depending upon the exit status of that command. If you want to test whether or not a file exists, you should use the test -e command instead of ls command:
if test -e foo.test.txt # The same as above, but using "test" instead of "ls"
then
echo "This file exists"
else
echo "I can't find it anywhere..."
fi
If the file exists, test -e will return an exit status of 0. Otherwise, it will return a non-zero exit status.
If you do this:
ls -i /bin/test /bin/[
10958 /bin/[ 10958 /bin/test
That 10958 is the inode. Files with the same inode are two different names for the same file. Thus [ and test command are soft linked1. This means you can use [ instead of test:
if [ -e foo.test.txt ]
then
echo "This file exists"
else
echo "I can't find it anywhere.."
fi
Does it look familiar?
1. In Bash, the test and [ are builtin, so when you run these commands in BASH, it isn't running /bin/test or /bin/[. However, they're still linked to each other.
They are not switches for the if statement, but for the test command ([ is a synonym for the test builtin). See help test in Bash for a complete list.
It's actually not if that's providing those — it's [, better known by the name of test. help test should give you a list of all options it can take. You could also look at the standard, if you care.
Related
I am trying to automate our application backup. Part of the process is to check the exit status of egrep in an if statement:
if [ ! -f /opt/apps/SiteScope_backup/sitescope_configuration.zip ] ||
[ egrep -i -q "error|warning|fatal|missing|critical" "$File" ]
then
echo "testing"
fi
I expected it to output testing because the file exists and egrep returns success, but instead I'm getting an error:
-bash: [: too many arguments
I tried with all kinds of syntax - additional brackets, quotes etc but error still persists.
Please help me in understanding where I am going wrong.
You are making the common mistake of assuming that [ is part of the if statement's syntax. It is not; the syntax of if is simply
if command; then
: # ... things which should happen if command's result code was 0
else
: # ... things which should happen otherwise
fi
One of the common commands we use is [ which is an alias for the command test. It is a simple command for comparing strings, numbers, and files. It accepts a fairly narrow combination of arguments, and tends to generate confusing and misleading error messages if you don't pass it the expected arguments. (Or rather, the error messages are adequate and helpful once you get used to it, but they are easily misunderstood if you're not used.)
Here, you want to check the result of the command egrep:
if [ ! -f /opt/apps/SiteScope_backup/sitescope_configuration.zip ] ||
egrep -i -q "error|warning|fatal|missing|critical" "$File"
then
echo "testing"
fi
In the general case, command can be a pipeline or a list of commands; then, the exit code from the final command is the status which if will examine, similarly to how the last command in a script decides the exit status from the script.
These compound commands can be arbitrarily complex, like
if read thing
case $thing in
'' | 'quit') false;;
*) true;;
esac
then ...
but in practice, you rarely see more than a single command in the if statement (though it's not unheard of; your compound statement with || is a good example!)
Just to spell this out,
if [ egrep foo bar ]
is running [ aka test on the arguments egrep foo bar. But [ without options only accepts a single argument, and then checks whether or not that argument is the empty string. (egrep is clearly not an empty string. Quoting here is optional, but would perhaps make it easier to see:
if [ "egrep" ]; then
echo "yes, 'egrep' is not equal to ''"
fi
This is obviously silly in isolation, but should hopefully work as an illustrative example.)
The historical reasons for test as a general kitchen sink of stuff the authors didn't want to make part of the syntax of if is one of the less attractive designs of the original Bourne shell. Bash and zsh offer alternatives which are less unwieldy (like the [[ double brackets in bash), and of course, POSIX test is a lot more well-tempered than the original creation from Bell Labs.
I am trying to automate our application backup. Part of the process is to check the exit status of egrep in an if statement:
if [ ! -f /opt/apps/SiteScope_backup/sitescope_configuration.zip ] ||
[ egrep -i -q "error|warning|fatal|missing|critical" "$File" ]
then
echo "testing"
fi
I expected it to output testing because the file exists and egrep returns success, but instead I'm getting an error:
-bash: [: too many arguments
I tried with all kinds of syntax - additional brackets, quotes etc but error still persists.
Please help me in understanding where I am going wrong.
You are making the common mistake of assuming that [ is part of the if statement's syntax. It is not; the syntax of if is simply
if command; then
: # ... things which should happen if command's result code was 0
else
: # ... things which should happen otherwise
fi
One of the common commands we use is [ which is an alias for the command test. It is a simple command for comparing strings, numbers, and files. It accepts a fairly narrow combination of arguments, and tends to generate confusing and misleading error messages if you don't pass it the expected arguments. (Or rather, the error messages are adequate and helpful once you get used to it, but they are easily misunderstood if you're not used.)
Here, you want to check the result of the command egrep:
if [ ! -f /opt/apps/SiteScope_backup/sitescope_configuration.zip ] ||
egrep -i -q "error|warning|fatal|missing|critical" "$File"
then
echo "testing"
fi
In the general case, command can be a pipeline or a list of commands; then, the exit code from the final command is the status which if will examine, similarly to how the last command in a script decides the exit status from the script.
These compound commands can be arbitrarily complex, like
if read thing
case $thing in
'' | 'quit') false;;
*) true;;
esac
then ...
but in practice, you rarely see more than a single command in the if statement (though it's not unheard of; your compound statement with || is a good example!)
Just to spell this out,
if [ egrep foo bar ]
is running [ aka test on the arguments egrep foo bar. But [ without options only accepts a single argument, and then checks whether or not that argument is the empty string. (egrep is clearly not an empty string. Quoting here is optional, but would perhaps make it easier to see:
if [ "egrep" ]; then
echo "yes, 'egrep' is not equal to ''"
fi
This is obviously silly in isolation, but should hopefully work as an illustrative example.)
The historical reasons for test as a general kitchen sink of stuff the authors didn't want to make part of the syntax of if is one of the less attractive designs of the original Bourne shell. Bash and zsh offer alternatives which are less unwieldy (like the [[ double brackets in bash), and of course, POSIX test is a lot more well-tempered than the original creation from Bell Labs.
I'm writing a bash script to organise a .txt file, of which is loaded with the bash script using the command line parameter:
bash ./myScript.sh textfile.txt
I have an if statement (below, non-functional) that's supposed to detect if the .txt file exists in the same directory. If it does, it confirms it with the user and the script continues. If it doesn't exist, the script is supposed to continually check if the user's input is an existing file in the working directory before continuing.
Here's what I have so far:
#CS101 Assignment BASH script
CARFILE=$1
wc $CARFILE
if [ -f $CARFILE ]
then
echo "$CARFILE exists, please continue"
else
echo "This file does not exist, please enter the new filename and press [ENTER]"
read CARFILE
echo "We have detected that you're using $CARFILE as your cars file, please continue."
fi
It simply outputs: exists, please continue if you don't run it with a .txt (ie bash jag32.sh instead of bash jag32.sh textfile.txt).
Can anyone help out please?
Thanks.
There are two problems here. First, you're calling wc $CARFILE before you check if $CARFILE exists. If you specify no paramaters (bash jag32.sh), then this becomes simply wc, which will wait forever for input on stdin.
Before going any further, bash -x is your friend: this will trace the execution of your script, showing you exactly what commands the script is running. This will often help illuminate problems:
bash -x jag32.sh
The problem with your test:
if [ -f $CARFILE ]
Is that if $CARFILE is empty, it becomes simply:
if [ -f ]
Which evaluates to true. What!? That's because the file-existence test takes two parameters (-f FILE), and when $CARFILE is empty, you only have one, so it's evaluating as a simple is-this-string-empty? test.
And this is why you always quote variables:
if [ -f "$CARFILE" ]
If $CARFILE is empty, this becomes:
if [ -f "" ]
Which will evaluate to false.
In addition to checking for the existence of the file, you could also first check if $1 has any value, or if your script has been passed any arguments. The bash(1) and test(1) man pages have details that should point you in the right direction.
I'm trying to write a simple script that will tell me if a filename exist in $Temp that starts with the string "Test".
For example, I have these files
Test1989.txt
Test1990.txt
Test1991.txt
Then I just want to echo that a file was found.
For example, something like this:
file="home/edward/bank1/fiche/Test*"
if test -s "$file"
then
echo "found one"
else
echo "found none"
fi
But this doesn't work.
One approach:
(
shopt -s nullglob
files=(/home/edward/bank1/fiche/Test*)
if [[ "${#files[#]}" -gt 0 ]] ; then
echo found one
else
echo found none
fi
)
Explanation:
shopt -s nullglob will cause /home/edward/bank1/fiche/Test* to expand to nothing if no file matches that pattern. (Without it, it will be left intact.)
( ... ) sets up a subshell, preventing shopt -s nullglob from "escaping".
files=(/home/edward/bank1/fiche/Test*) puts the file-list in an array named files. (Note that this is within the subshell only; files will not be accessible after the subshell exits.)
"${#files[#]}" is the number of elements in this array.
Edited to address subsequent question ("What if i also need to check that these files have data in them and are not zero byte files"):
For this version, we need to use -s (as you did in your question), which also tests for the file's existence, so there's no point using shopt -s nullglob anymore: if no file matches the pattern, then -s on the pattern will be false. So, we can write:
(
found_nonempty=''
for file in /home/edward/bank1/fiche/Test* ; do
if [[ -s "$file" ]] ; then
found_nonempty=1
fi
done
if [[ "$found_nonempty" ]] ; then
echo found one
else
echo found none
fi
)
(Here the ( ... ) is to prevent file and found_file from "escaping".)
You have to understand how Unix interprets your input.
The standard Unix shell interpolates environment variables, and what are called globs before it passes the parameters to your program. This is a bit different from Windows which makes the program interpret the expansion.
Try this:
$ echo *
This will echo all the files and directories in your current directory. Before the echo command acts, the shell interpolates the * and expands it, then passes that expanded parameter back to your command. You can see it in action by doing this:
$ set -xv
$ echo *
$ set +xv
The set -xv turns on xtrace and verbose. Verbose echoes the command as entered, and xtrace echos the command that will be executed (that is, after the shell expansion).
Now try this:
$ echo "*"
Note that putting something inside quotes hides the glob expression from the shell, and the shell cannot expand it. Try this:
$ foo="this is the value of foo"
$ echo $foo
$ echo "$foo"
$ echo '$foo'
Note that the shell can still expand environment variables inside double quotes, but not in single quotes.
Now let's look at your statement:
file="home/edward/bank1/fiche/Test*"
The double quotes prevent the shell from expanding the glob expression, so file is equal to the literal home/edward/bank1/finche/Test*. Therefore, you need to do this:
file=/home/edward/bank1/fiche/Test*
The lack of quotes (and the introductory slash which is important!) will now make file equal to all files that match that expression. (There might be more than one!). If there are no files, depending upon the shell, and its settings, the shell may simply set file to that literal string anyway.
You certainly have the right idea:
file=/home/edward/bank1/fiche/Test*
if test -s $file
then
echo "found one"
else
echo "found none"
fi
However, you still might get found none returned if there is more than one file. Instead, you might get an error in your test command because there are too many parameters.
One way to get around this might be:
if ls /home/edward/bank1/finche/Test* > /dev/null 2>&1
then
echo "There is at least one match (maybe more)!"
else
echo "No files found"
fi
In this case, I'm taking advantage of the exit code of the ls command. If ls finds one file it can access, it returns a zero exit code. If it can't find one matching file, it returns a non-zero exit code. The if command merely executes a command, and then if the command returns a zero, it assumes the if statement as true and executes the if clause. If the command returns a non-zero value, the if statement is assumed to be false, and the else clause (if one is available) is executed.
The test command works in a similar fashion. If the test is true, the test command returns a zero. Otherwise, the test command returns a non-zero value. This works great with the if command. In fact, there's an alias to the test command. Try this:
$ ls -li /bin/test /bin/[
The i prints out the inode. The inode is the real ID of the file. Files with the same ID are the same file. You can see that /bin/test and /bin/[ are the same command. This makes the following two commands the same:
if test -s $file
then
echo "The file exists"
fi
if [ -s $file ]
then
echo "The file exists"
fi
You can do it in one line:
ls /home/edward/bank1/fiche/Test* >/dev/null 2>&1 && echo "found one" || echo "found none"
To understand what it does you have to decompose the command and have a basic awareness of boolean logic.
Directly from bash man page:
[...]
expression1 && expression2
True if both expression1 and expression2 are true.
expression1 || expression2
True if either expression1 or expression2 is true.
[...]
In the shell (and in general in unix world), the boolean true is a program that exits with status 0.
ls tries to list the pattern, if it succeed (meaning the pattern exists) it exits with status 0, 2 otherwise (have a look at ls man page for details).
In our case there are actually 3 expressions, for the sake of clarity I will put parenthesis, although they are not needed because && has precedence on ||:
(expression1 && expression2) || expression3
so if expression1 is true (ie: ls found the pattern) it evaluates expression2 (which is just an echo and will exit with status 0). In this case expression3 is never evaluate because what's on the left site of || is already true and it would be a waste of resources trying to evaluate what's on the right.
Otherwise, if expression1 is false, expression2 is not evaluated but in this case expression3 is.
for entry in "/home/loc/etc/"/*
do
if [ -s /home/loc/etc/$entry ]
then
echo "$entry File is available"
else
echo "$entry File is not available"
fi
done
Hope it helps
The following script will help u to go to a process if that script exist in a specified variable,
cat > waitfor.csh
#!/bin/csh
while !( -e $1 )
sleep 10m
end
ctrl+D
here -e is for working with files,
$1 is a shell variable,
sleep for 10 minutes
u can execute the script by ./waitfor.csh ./temp ; echo "the file exits"
One liner to check file exist or not -
awk 'BEGIN {print getline < "file.txt" < 0 ? "File does not exist" : "File Exists"}'
Wildcards aren't expanded inside quoted strings. And when wildcard is expanded, it's returned unchanged if there are no matches, it doesn't expand into an empty string. Try:
output="$(ls home/edward/bank1/fiche/Test* 2>/dev/null)"
if [ -n "$output" ]
then echo "Found one"
else echo "Found none"
fi
If the wildcard expanded to filenames, ls will list them on stdout; otherwise it will print an error on stderr, and nothing on stdout. The contents of stdout are assigned to output.
if [ -n "$output" ] tests whether $output contains anything.
Another way to write this would be:
if [ $(ls home/edward/bank1/fiche/Test* 2>/dev/null | wc -l) -gt 0 ]
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