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?

+3


source to share


4 answers


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/[

.

+5


source


Use -a

instead of &&

and do not add double quotes around expressions such as "! -f /filename"

. Also note that it -f /data/sb3????????sh25t.sqfs

doesn't work if there are multiple files matching the pattern.



0


source


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:

Simple Boolean Operators in Bash

0


source


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.

0


source







All Articles