Why am I getting 'missing`]' 'from this bash script to the file list?
I have the following code
#!/bin/bash
if [ "-f /data/sb3???sh25t.sqfs" && "! -f /data/sb3??????sh25t.sqfs" && "! -f /data/sb3????????sh25t.sqfs" && "! -f /data/sb3???????sh25t.sqfs" ] ;then
select build in `ls -D /data/tftpboot/sb3???sh25t.sqfs` ; do
break
done
build=${build:15}
fi
When I run this script it gives me an error:
missing `] '
How can I solve this?
source to share
Okay, make yourself a can of Jolt Cola and sit down. It will take some time...
A if
Unix shell statement accepts a command (any command), processes that command, and executes the then statement of an if statement if that command returns a zero exit code.If this command returns a nonzero value, it will execute the else clause if it exists.
For example:
if ls foo 2>&1 > /dev/null
then
echo "File 'foo' exists"
else
echo "No Foo For you!"
fi
The above command runs the command ls foo
. The output will be removed with 2>&1 > /dev/null
, so you won't see a list if it foo
exists, and you won't get an error if it foo
doesn't exist. However, the command ls
will still return 0
if it foo
exists, and 1
if it doesn't exist.
This allows you to do all sorts of neat things:
if mv foo foo.backup
then
echo "Renamed `foo` directory to `foo.backup`"
else
echo "BIG HORKING ERROR: Can't rename 'foo' to 'foo.backup'"
exit 2 #I need to stop my program. This is a big problem.
fi
In the above code snippet, I am trying to rename my directory foo
to foo.backup
before I put in a new directory foo
. The directory foo
may not exist, which is why the command mv
fails. I may not have permission to rename the directory. Be that as it may, the operator if
allows me to check if my command is executed mv
or not.
That's all the command does if
: it executes the command and depends on whether the return value is null or not, it may or may not execute the then clause.
But what if I want to check if a particular condition exists? For example:
- Is there a directory named
foo
? - Is the environment variable
$value
more than 3?
Wouldn't it be nice if there was some team that would let me test these things? Hey, if this command returns null, if my test is true and nonzero, if my test is if false, I could include it in the statement if
!
Fortunately, there is a command in the Unix call that does exactly that:
if test -d foo
then
echo "Directory foo exists"
fi
if test $value -gt 3
then
echo "\$value is bigger than 3"
fi
It also happens that the Unix command test
has a hard link to the command [
:
$ cd /bin
$ ls -li test [
54008404 -rwxr-xr-x 2 root wheel 18576 Jul 25 2012 [
54008404 -rwxr-xr-x 2 root wheel 18576 Jul 25 2012 test
The first column is the inode number . Since both are the same inode numbers, the command [
is just a hard link to the command test
.
It's a little easier to understand now. [
1 is actually a Unix command! This is why I need the space between the square brackets and what I am testing:
# Invalid Syntax: I need spaces between the test and the braces:
if [$value -gt 3]
# Valid Syntax
if [ $value -gt 3 ]
# Actually the same command as above
if test value -gt 3
It also makes clear why he -gt
and -f
a leading dash, not just gt
or f
. This is because, -gt
and -f
are the parameters of the test team!
Now that you have a clearer understanding of how the built-in shell works if
, and how [
it really is a Unix command, we can look at your if statement and see what the problem is:
if [ "-f "/data/sb3???sh25t.sqfs" && "! -f /data/sb3??????sh25t.sqfs" && \
"! -f /data/sb3????????sh25t.sqfs" && "! -f /data/sb3???????sh25t.sqfs" ]
First you have:
"-f /data/sb3???sh25t.sqfs"
The quotation marks make the whole string one single parameter to the test command, which is not what you want. You want the parameter to -f
be separate from the file you are testing. So you need to remove -f
from the quotes, so the test command sees this as a parameter:
if [ -f "/data/sb3???sh25t.sqfs" && ! -f "/data/sb3??????sh25t.sqfs" && \
! -f "/data/sb3????????sh25t.sqfs" && ! -f "/data/sb3???????sh25t.sqfs" ]
It still won't work. Take a look at the (manpage) [http://www.manpagez.com/man/1/test/] for the command test
. Note that &&
this is not a valid parameter for the command test
. Instead, you can use -a
to combine the two expressions, your testing:
if [ -f "/data/sb3???sh25t.sqfs" -a ! -f "/data/sb3??????sh25t.sqfs" -a \
! -f "/data/sb3????????sh25t.sqfs" -a ! -f "/data/sb3???????sh25t.sqfs" ]
then
-f
is outside the quotes (as well !
) and I use -a
for all four conditions together.
I could also do something like this:
if [ -f "/data/sb3???sh25t.sqfs" ] && [ ! -f "/data/sb3??????sh25t.sqfs" ] && \
! -f "/data/sb3????????sh25t.sqfs" ] && [ ! -f "/data/sb3???????sh25t.sqfs" ]
&&
is actually a bash shell structure called list operator. It combines two separate commands and does the following:
- Run the first command.
- If the first command returns nonzero, run the second command. Otherwise, return the exit code of the first command and do not run the second command.
For example, quite often you see something like this:
[ -d foo ] && rm -rf foo
or (the same):
test -d foo && rm -rf foo
In the commands above, I test if foo
it exists as a directory. If it exists, my test command returns true and then I run the second command which deletes the directory. This is the same as ...
if [ -d foo ]
then
rm -rf foo
fi
Thus, the my if statement consists of four separate commands. The first one [...]
executes and sees if the file exists /data/sb3???sh25t.sqfs
. If so, this test is correct, returns a zero exit code. Then it &&
runs the next command in the list.
The following testing command sees if there is /data/sb3??????sh25t.sqfs
... If this file does not exist, the test command returns a zero exit code and the next statement in the list &&
runs the next test command. Only if the first three commands test
are correct, the last command of the test is executed. If the last command of the test is also true, then your then clause will be executed.
I hope you understand how it works if
and why you are having problems.
Unfortunately, there is another problem that is happening, and how the Unix shell works and how globbing interacts with the command line.
Unlike its counterparts on other operating systems (Windows cough! Cough), the Unix shell is very smart and also very slow.
Try this command from the command line:
$ echo "Print an *" #Use quotes
Print an *
Now try this:
$ echo Print an * #No quotes
Print an bin foo bar...
This will print all "Print A" followed by all files in your current directory.
Try the following:
$ touch foo.out foo.txt
$ echo "Do I have any foo.??? files"
Do I have any foo.??? files
$ echo Do I have any foo.??? files
Do I have any foo.out foo.txt files
Again, without the quotes, what I asked to print was changed. This is because the Unix shell gets in the way. What happens is that the Unix shell sees the globbing pattern foo.???
and replaces that pattern with all files before the command echo
can repeat anything.
This can be seen with the following:
$ set -x #Turns on `xtrace`
$ touch foo.out foo.txt
+ touch foo.out foo.txt
$ echo "Do I have any foo.??? files"
+ echo "Do I have any foo.??? files"
Do I have any foo.??? files
$ echo Do I have any foo.??? files
+ echo Do I have any foo.out foo.txt files
Do I have any foo.out foo.txt files
$ set +x #Turns off `xtrace`
The function xtrace
shows you that the command you are going to run after is shutting down the shell with your command. The lines starting with +
show you what your command will actually execute.
The reason I am doing this is due to a possible problem. If you have multiple files matching your pattern, all files matching your pattern will be replaced in your test command. Let's take a look at the command again:
if [ -f /data/sb3???sh25t.sqfs ] && [ ! -f /data/sb3??????sh25t.sqfs ] && \
[ ! -f /data/sb3????????sh25t.sqfs ] && [ ! -f /data/sb3???????sh25t.sqfs ]
If you have a file /data/sb3aaash25t.sqfs
and one named /data/sb3bbbsh25t.sqfs
, the first test command in your if statement will look something like this:
if [-f /data/sb3aaash25t.sqfs /data/sb3bbbsh25t.sqfs ]
which may not work as the parameter -f
only takes one filename. Thus, even if you do everything mentioned above, you can still end up with a non-working program. Worse, it will be intermittent, which is even more difficult to debug.
One way is to forget about the testing command and use the command ls
as you saw at the very top of this answer:
if ls /data/sb3???25t.sqfs 2>&1 > /dev/null
then
echo "At least one file that matches pattern sb3???25t.sqfs exists"
fi
Thus, you can do this:
if ls /data/sb3???sh25t.sqfs 2>&1 > /dev/null && \
! ls /data/sb3??????sh25t.sqfs 2>&1 > /dev/null && \
! ls /data/sb3????????sh25t.sqfs 2>&1 > /dev/null && \
! ls /data/sb3???????sh25t.sqfs 2>&1 > /dev/null
then
as it ls
can accept more than one file.
I am sorry for the long explanation. 99% of the time, you can continue your fun path without understanding how the operator works if
, or what [...]
is really a completely separate Unix command from if
, or how this annoying Unix shell will let you go.
It just happened that you ran into a jackpot with this question. If you were playing Lotto today instead of writing your script, you could tell your boss what you really think of them, your job, and move to a tropical island paradise where the culture is so primitive that there are no words for a terminal emulator.
Instead, he will return to the salt mines tomorrow.
1 Yes, yes. I know it [
is built into the bash shell and is not a command /bin/[
. However, a shell built in [
acts like a command /bin/[
.
source to share
Also you have a &&
singe inside [
]
It is not allowed
Use either [[
and ]]
or output &&
like this.
[ -f file1 ] && [ -f file2 ]
Since you are using globes, you cannot use the syntax below:
<s> But it is better to use
[[ -f file1 && -f file2 ]]
Please note that the later version only works in bash
here:
source to share
Your code is better written like
#!/bin/bash
if [ -f /data/sb3???sh25t.sqfs ] &&
[ ! -f /data/sb3??????sh25t.sqfs ] &&
[ ! -f /data/sb3???????sh25t.sqfs ] &&
[ ! -f /data/sb3????????sh25t.sqfs ]; then
select build in /data/tftpboot/sb3???sh25t.sqfs ; do
break
done
build="${build:15}"
fi
(I changed the order of the tests f
to make it easier to see the pattern in the filenames you tested.)
Don't quote operators -f
(but most likely name those operands) and put each test in a separate instruction test/[
. No need to run ls
in your command select
as the template extension already creates a list of files to choose from; ls
will just take that list and play it, so it doesn't work.
source to share