Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shell script to check if file exists

Tags:

shell

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.

like image 921
Edward Avatar asked Mar 09 '13 00:03

Edward


2 Answers

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".)

like image 125
ruakh Avatar answered Oct 18 '22 18:10

ruakh


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
like image 23
David W. Avatar answered Oct 18 '22 20:10

David W.