This checks if a file exists:
#!/bin/bash
FILE=$1
if [ -f $FILE ]; then
echo "File $FILE exists."
else
echo "File $FILE does not exist."
fi
How do I only check if the file does not exist?
The test command (written as [ here) has a "not" logical operator, ! (exclamation mark):
if [ ! -f /tmp/foo.txt ]; then
echo "File not found!"
fi
Bash File Testing
-b filename - Block special file
-c filename - Special character file
-d directoryname - Check for directory Existence
-e filename - Check for file existence, regardless of type (node, directory, socket, etc.)
-f filename - Check for regular file existence not a directory
-G filename - Check if file exists and is owned by effective group ID
-G filename set-group-id - True if file exists and is set-group-id
-k filename - Sticky bit
-L filename - Symbolic link
-O filename - True if file exists and is owned by the effective user id
-r filename - Check if file is a readable
-S filename - Check if file is socket
-s filename - Check if file is nonzero size
-u filename - Check if file set-user-id bit is set
-w filename - Check if file is writable
-x filename - Check if file is executable
How to use:
#!/bin/bash
file=./file
if [ -e "$file" ]; then
echo "File exists"
else
echo "File does not exist"
fi
A test expression can be negated by using the ! operator
#!/bin/bash
file=./file
if [ ! -e "$file" ]; then
echo "File does not exist"
else
echo "File exists"
fi
Negate the expression inside test (for which [ is an alias) using !:
#!/bin/bash
FILE=$1
if [ ! -f "$FILE" ]
then
echo "File $FILE does not exist"
fi
The relevant man page is man test or, equivalently, man [ -- or help test or help [ for the built-in bash command.
Alternatively (less commonly used) you can negate the result of test using:
if ! [ -f "$FILE" ]
then
echo "File $FILE does not exist"
fi
That syntax is described in "man 1 bash" in sections "Pipelines" and "Compound Commands".
[[ -f $FILE ]] || printf '%s does not exist!\n' "$FILE"
Also, it's possible that the file is a broken symbolic link, or a non-regular file, like e.g. a socket, device or fifo. For example, to add a check for broken symlinks:
if [[ ! -f $FILE ]]; then
if [[ -L $FILE ]]; then
printf '%s is a broken symlink!\n' "$FILE"
else
printf '%s does not exist!\n' "$FILE"
fi
fi
It's worth mentioning that if you need to execute a single command you can abbreviate
if [ ! -f "$file" ]; then
echo "$file"
fi
to
test -f "$file" || echo "$file"
or
[ -f "$file" ] || echo "$file"
I prefer to do the following one-liner, in POSIX shell compatible format:
$ [ -f "/$DIR/$FILE" ] || echo "$FILE NOT FOUND"
$ [ -f "/$DIR/$FILE" ] && echo "$FILE FOUND"
For a couple of commands, like I would do in a script:
$ [ -f "/$DIR/$FILE" ] || { echo "$FILE NOT FOUND" ; exit 1 ;}
Once I started doing this, I rarely use the fully typed syntax anymore!!
To test file existence, the parameter can be any one of the following:
-e: Returns true if file exists (regular file, directory, or symlink)
-f: Returns true if file exists and is a regular file
-d: Returns true if file exists and is a directory
-h: Returns true if file exists and is a symlink
All the tests below apply to regular files, directories, and symlinks:
-r: Returns true if file exists and is readable
-w: Returns true if file exists and is writable
-x: Returns true if file exists and is executable
-s: Returns true if file exists and has a size > 0
Example script:
#!/bin/bash
FILE=$1
if [ -f "$FILE" ]; then
echo "File $FILE exists"
else
echo "File $FILE does not exist"
fi
You can do this:
[[ ! -f "$FILE" ]] && echo "File doesn't exist"
or
if [[ ! -f "$FILE" ]]; then
echo "File doesn't exist"
fi
If you want to check for file and folder both, then use -e option instead of -f. -e returns true for regular files, directories, socket, character special files, block special files etc.
You should be careful about running test for an unquoted variable, because it might produce unexpected results:
$ [ -f ]
$ echo $?
0
$ [ -f "" ]
$ echo $?
1
The recommendation is usually to have the tested variable surrounded by double quotation marks:
#!/bin/sh
FILE=$1
if [ ! -f "$FILE" ]
then
echo "File $FILE does not exist."
fi
In
[ -f "$file" ]
the [ command does a stat() (not lstat()) system call on the path stored in $file and returns true if that system call succeeds and the type of the file as returned by stat() is "regular".
So if [ -f "$file" ] returns true, you can tell the file does exist and is a regular file or a symlink eventually resolving to a regular file (or at least it was at the time of the stat()).
However if it returns false (or if [ ! -f "$file" ] or ! [ -f "$file" ] return true), there are many different possibilities:
the file doesn't exist
the file exists but is not a regular file (could be a device, fifo, directory, socket...)
the file exists but you don't have search permission to the parent directory
the file exists but that path to access it is too long
the file is a symlink to a regular file, but you don't have search permission to some of the directories involved in the resolution of the symlink.
... any other reason why the stat() system call may fail.
In short, it should be:
if [ -f "$file" ]; then
printf '"%s" is a path to a regular file or symlink to regular file\n' "$file"
elif [ -e "$file" ]; then
printf '"%s" exists but is not a regular file\n' "$file"
elif [ -L "$file" ]; then
printf '"%s" exists, is a symlink but I cannot tell if it eventually resolves to an actual file, regular or not\n' "$file"
else
printf 'I cannot tell if "%s" exists, let alone whether it is a regular file or not\n' "$file"
fi
To know for sure that the file doesn't exist, we'd need the stat() system call to return with an error code of ENOENT (ENOTDIR tells us one of the path components is not a directory is another case where we can tell the file doesn't exist by that path). Unfortunately the [ command doesn't let us know that. It will return false whether the error code is ENOENT, EACCESS (permission denied), ENAMETOOLONG or anything else.
The [ -e "$file" ] test can also be done with ls -Ld -- "$file" > /dev/null. In that case, ls will tell you why the stat() failed, though the information can't easily be used programmatically:
$ file=/var/spool/cron/crontabs/root
$ if [ ! -e "$file" ]; then echo does not exist; fi
does not exist
$ if ! ls -Ld -- "$file" > /dev/null; then echo stat failed; fi
ls: cannot access '/var/spool/cron/crontabs/root': Permission denied
stat failed
At least ls tells me it's not because the file doesn't exist that it fails. It's because it can't tell whether the file exists or not. The [ command just ignored the problem.
With the zsh shell, you can query the error code with the $ERRNO special variable after the failing [ command, and decode that number using the $errnos special array in the zsh/system module:
zmodload zsh/system
ERRNO=0
if [ ! -f "$file" ]; then
err=$ERRNO
case $errnos[err] in
("") echo exists, not a regular file;;
(ENOENT|ENOTDIR)
if [ -L "$file" ]; then
echo broken link
else
echo does not exist
fi;;
(*) syserror -p "can't tell: " "$err"
esac
fi
(beware the $errnos support was broken with some versions of zsh when built with recent versions of gcc).
There are three distinct ways to do this:
Negate the exit status with bash (no other answer has said this):
if ! [ -e "$file" ]; then
echo "file does not exist"
fi
Or:
! [ -e "$file" ] && echo "file does not exist"
Negate the test inside the test command [ (that is the way most answers before have presented):
if [ ! -e "$file" ]; then
echo "file does not exist"
fi
Or:
[ ! -e "$file" ] && echo "file does not exist"
Act on the result of the test being negative (|| instead of &&):
Only:
[ -e "$file" ] || echo "file does not exist"
This looks silly (IMO), don't use it unless your code has to be portable to the Bourne shell (like the /bin/sh of Solaris 10 or earlier) that lacked the pipeline negation operator (!):
if [ -e "$file" ]; then
:
else
echo "file does not exist"
fi
envfile=.env
if [ ! -f "$envfile" ]
then
echo "$envfile does not exist"
exit 1
fi
To reverse a test, use "!".
That is equivalent to the "not" logical operator in other languages. Try this:
if [ ! -f /tmp/foo.txt ];
then
echo "File not found!"
fi
Or written in a slightly different way:
if [ ! -f /tmp/foo.txt ]
then echo "File not found!"
fi
Or you could use:
if ! [ -f /tmp/foo.txt ]
then echo "File not found!"
fi
Or, presing all together:
if ! [ -f /tmp/foo.txt ]; then echo "File not found!"; fi
Which may be written (using then "and" operator: &&) as:
[ ! -f /tmp/foo.txt ] && echo "File not found!"
Which looks shorter like this:
[ -f /tmp/foo.txt ] || echo "File not found!"
The test thing may count too. It worked for me (based on Bash Shell: Check File Exists or Not):
test -e FILENAME && echo "File exists" || echo "File doesn't exist"
This code also working .
#!/bin/bash
FILE=$1
if [ -f $FILE ]; then
echo "File '$FILE' Exists"
else
echo "The File '$FILE' Does Not Exist"
fi
The simplest way
FILE=$1
[ ! -e "${FILE}" ] && echo "does not exist" || echo "exists"
This shell script also works for finding a file in a directory:
echo "enter file"
read -r a
if [ -s /home/trainee02/"$a" ]
then
echo "yes. file is there."
else
echo "sorry. file is not there."
fi
sometimes it may be handy to use && and || operators.
Like in (if you have command "test"):
test -b $FILE && echo File not there!
or
test -b $FILE || echo File there!
If you want to use test instead of [], then you can use ! to get the negation:
if ! test "$FILE"; then
echo "does not exist"
fi
You can also group multiple commands in the one liner
[ -f "filename" ] || ( echo test1 && echo test2 && echo test3 )
or
[ -f "filename" ] || { echo test1 && echo test2 && echo test3 ;}
If filename doesn't exit, the output will be
test1
test2
test3
Note: ( ... ) runs in a subshell, { ... ;} runs in the same shell.
I have the following script file that writes files to s3 from a local file system:
#!/bin/bash
CURR_DIR=`dirname $0`
SCRIPT_NAME="$(basename $0)"
LOG_FILE=$(echo $SCRIPT_NAME | cut -f 1 -d '.')
TODAY=$(date '+%Y-%m-%d')
NOW=$(date -d "$(date +%Y-%m-%d)" +%Y"-"%m"-"%d)
LOG_PATH="$CURR_DIR"/logs/"$LOG_FILE"-$TODAY.log
LOG="[$(date '+%Y-%m-%d %H:%M:%S,%3N')] INFO {$LOG_FILE} -"
ERROR_LOG="[$(date '+%Y-%m-%d %H:%M:%S,%3N')] ERROR {$LOG_FILE} -"
BUCKET="s3.bucket.example"
OUT_FOLDER="path/to/folderA"
S3_PUSH="s3://$BUCKET/$OUT_FOLDER"
exec &>> $LOG_PATH
echo "$LOG Copying files to local out folder..." >> $LOG_PATH
cp /path/to/folderA/*.* /path/to/folderB
echo "$LOG Command returned code:" $?
if [ "$(ls -A path/to/folderA/)" ]; then
FILES="$(ls path/to/folderA/*)"
for file in $FILES ; do
echo "$LOG File $file found for sync" >> $LOG_PATH
echo "$LOG Pushing $file to S3 /Folder..." >> $LOG_PATH
echo -n "$LOG " ; s3cmd put -c /home/config/.s3cfg "$file" "$S3_PUSH"/
echo "$LOG Command returned code:" $?
echo "$LOG Copying $file to local backup..." >> $LOG_PATH
mv "$file" /path/to/folderA/backup/
echo "$LOG Command returned code:" $? >> $LOG_PATH
RCC=$?
if [ $? -eq 0 ]
then
echo "$LOG Command returned code:" $?
else
echo "$ERROR_LOG Command returned code:" $?
fi
done
else
echo "$LOG No files found for sync." >> $LOG_PATH
fi
And the output is coming out in a specific grok pattern needed for me to parse this output as logs into Elastic Search, however the line 27 output is as follows:
[2021-09-02 08:15:25,629] INFO {TestGrokScriptPattern} - upload: '/path/to/folderA/File.txt' -> 's3://s3.bucket.example/Path/To/Bucket/File.txt' [1 of 1]
0 of 0 0% in 0s 0.00 B/s done
that upload and 0 of 0 0%... Line is created by the exec & command executed on line 16.
How can I get that output to not go to the next line without the date, time and script name preceeding it in order to not break the log pattern I am trying to create?
Rather than redirect output on each line, you can wrap the body of the script in a single block and then handle the output of the entire block in one place. You can then process that output with the stream editor sed. For example:
if true; then # Always true. Just simplifies redirection.
echo "Doing something..."
command_with_output
command_with_more_output
echo "Done."
fi | sed "s/^/${LOG}/" > ${LOG_PATH} 2>&1
The sed expression means: Substitute (s) the beginning of each line (^) with the contents of the LOG variable.
Using 2>&1 at the end also eliminates the need for the exec &>> $LOG_PATH command.
I am trying to check if a user types multiple arguments in a command line using case and if/else statements. What's wrong is that I keep getting the default case instead of the same command, but with 2 more arguments. For instance, one of my conditions is:
del)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: removes a file"
else
echo "using Bash command: rm $2 $3"
rm $2 $3
echo done
fi
prints the first condition, but if I type, say, del aaa bbb, I get the default case, which is:
echo "ERROR: Unrecognized command"
I'm also using this to read a user's input, if that helps.
read -p "wcl> " -r wcl $2 $3
I don't really know if there's a better way to solve this without scrapping all my code and starting from scratch.
This is the full code:
#!/bin/bash
#use read command
echo Welcome to the Windows Command Line simulator!
echo Enter your commands below
while true
do
read -p "wcl> " -r wcl $2 $3
case $wcl in
dir)
echo "using Bash command: ls $2 $3"
ls
continue
;;
copy)
FILE="$2"
if [ "$#" -ne 3 ]
then
echo "Usage: copy sourcefile destinationfile"
else
echo "using Bash command: cp $2 $3"
if [ -f "$FILE" ]
then
cp $2 $3
else
echo "cannot stat $FILE: No such file or directory">&2
fi
echo done
fi
continue
;;
del)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: removes a file"
else
echo "using Bash command: rm $2 $3"
rm $2 $3
echo done
fi
continue
;;
move)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: moves a file to another file name and location"
else
echo "using Bash command: mv $2 $3"
mv $2 $3
echo done
fi
continue
;;
rename)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: renames a file"
else
echo "using Bash command: mv $2 $3"
mv $2 $3
echo done
fi
continue
;;
ipconfig)
ifconfig eth0 | grep "inet addr" | cut -d ':' -f 2 | cut -d ' ' -f 1
continue
;;
exit)
echo "Goodbye"
exit 1
;;
^c)
echo "Goodbye"
exit 1
;;
*)
echo "ERROR: Unrecognized command"
continue
esac
done
You can't use read to set the positional parameters, although it isn't clear why you would need to here. Just use regular parameters.
while true
do
read -p "wcl> " -r wcl arg1 arg2
case $wcl in
dir)
echo "using Bash command: ls $arg1 $arg2"
ls "$arg1" "$arg2"
continue
;;
# ...
esac
done
The way read -r wcl $2 $3 is executed is that $2 and $3 are first expanded to give names that read will use to set variables. If those aren't set, then the command reduces to read -r wcl, and so your entire command line is assigned to the variable wcl, not just the command.
However, read by itself is not going to do the same parsing that the shell already does, if you goal is to write your own shell.
If you are really using bash, you can insert the words you read into positional parameters through an array. (You could also just leave them in the array, but the syntax for referring to positional parameters is simpler.)
# -a: read the successive words into an array
read -r -p "wcl> " -a params
# set the positional parameters to the expansion of the array
set -- "${params[#]}"
wcl=$1 # Or you could do the case on "$1"
This will also set $# to the number of words read, as a side-effect of setting the positional parameters.
As #chepner points outs, the read is problematic: It simply splits the input into whitespace-separated words, without respecting quotes, backslashes, and whatever other shell metacharacters you might want to implement. Doing a full bash-style parse of a command-line in bash itself would be quite a difficult exercise.
I have to write a script in linux that saves one line of text to a file and then appends a new line. What I currently have is something like:
read "This line will be saved to text." Text1
$Text1 > $Script.txt
read "This line will be appended to text." Text2
$Text2 >> $Script.txt
One of the main benefits of scripting is that you can automate processes. Using
read like you have destroys that. You can accept input from the user without
losing automation:
#!/bin/sh
if [ "$#" != 3 ]
then
echo 'script.sh [Text1] [Text2] [Script]'
exit
fi
printf '%s\n' "$1" "$2" > "$3"
Assuming you don't mind if the second line of your output file is overwritten (not appended) every time the script is run; this might do.
#!/bin/sh
output_file=output.dat
if [ -z "$1" ] || [ -z "$2" ]; then echo Need at least two arguments.; fi
line1=$1; line2=$2
echo $line1 > $output_file
echo $line2 >> $output_file
Executing the script:
# chmod +x foo.sh
# ./foo.sh
Need at least two arguments.
# ./foo.sh hello world
# cat output.dat
hello
world
I'm trying to check the type of a file using file, and then using the cut command to get only the type such as JPEG in my case, and then using it to check whether or not the file is the desired type. However whenever I run this in Shell it spits out pic1.jpg: Unexpected Operator. I'm not sure where the problem is and it's been boggling me for a while now.
!#/bin/sh
file=$(file -F " " $1)
if [ $file = ERROR: ] || [ $file = empty ] then
echo "$1 is not a valid jpeg file." >&2 >> error.log
else
extension=$(file -F " " $1 | cut -f 3 -d " ")
Note that you'll have to enclose $file in double quotes in the if statement. Otherwise the contents of $file will be interpreted as commands by the shell. This is one of the major pitfalls of shell coding you should be aware of.
In addition you where missing the ; between the ] and the then statements. Note that [ ... ] is the same as the test command. ] is just the last argument to it. Not 'just a bracket' like you would expect in 'normal languages' :). So, if there are two expressions on the same line the will have to be separated by an ;
Although I could suggest some enhancements to the I kept my example as much the same as yours to point you to the error. The following code will work:
#!/bin/sh
file=$(file -F " " $1)
if [ "$file" = "ERROR:" ] || [ "$file" = "empty" ] ; then
echo "$1 is not a valid jpeg file." 1>&2 >> error.log
else
extension=$(file -F " " $1 | cut -f 3 -d " ")
fi
echo "$extension"
Note that without double quotes your script would lead to the following if statement:
if [ test.sh POSIX shell script, ASCII text executable = Error: ] || [ test.sh POSIX shell script, ASCII text executable = empty ]