Shell Script check if file exists, and has read permissions - bash

In my shell script i'm trying to check if a specific file is exists and if it has reading permissions.
My file's path has spaces in it.
I quoted the file path:
file='/my/path/with\ some\ \spaces/file.txt'
This is the function to check if the file exists:
#Check if file exists and is readable
checkIfFileExists() {
#Check if file exists
if ! [ -e $1 ]; then
error "$1 does not exists";
fi
#Check if file permissions allow reading
if ! [ -r $1 ]; then
error "$1 does not allow reading, please set the file permissions";
fi
}
Here I double quote to make sure it gets the file as a one argument:
checkIfFileExists "'$file'";
And I receive an error from the bash saying:
[: too many arguments
Which make me thinks it doesn't get it as a one argument.
But in my custom error, I do get the whole path, and it says it doesn't exists.
Error: '/my/path/with\ some\ \spaces/file.txt' does not exists
Although it does exists, and when I tried to read it with "cat $file" I get a permission error..
what am I'm doing wrong?

The proper way to quote when you require variable interpolation is with double quotes:
if [ -e "$1" ]; then
You need similar quoting throughout the script, and the caller needs to quote or escape the string -- but not both. When you assign it, use one of these:
file='/my/path/with some spaces/file.txt'
# or
file=/my/path/with\ some\ spaces/file.txt
# or
file="/my/path/with some spaces/file.txt"
then use double quotes around the value to pass it in as a single argument:
checkIfFileExists "$file"
Again, where you need the variable's value to be interpolated, use double quotes.
For a quick illustration of what these quotes do, try this:
vnix$ printf '<<%s>>\n' "foo bar" "'baz quux'" '"ick poo"' \"ick poo\" ick\ poo
<<foo bar>>
<<'baz quux'>>
<<"ick poo">>
<<"ick>>
<<poo">>
<<ick poo>>
Furthermore, see also When to wrap quotes around a shell variable?

if [[ -e $1 ]];then
echo it exists
else
echo it doesnt
fi
if [[ -r $1 ]];then
echo readable
else
echo not readable
fi

Related

Use value of variable literally in if statement (bash) [duplicate]

This question already has answers here:
How to manually expand a special variable (ex: ~ tilde) in bash
(19 answers)
Closed 3 years ago.
I need to check if the value of a variable is a path that exists. This is being read from a text file.
Basically, the point I'm stuck at, the line variable is as follows: location_of_folder=~/Desktop/folder\ with\ spaces
I need to check if the path after location_of_folder= exists.
Here's what I've tried:
foo="${line#'location_of_folder='}"
if ! [[ -d "${foo}" ]]
then
echo 'This path exists.'
else
echo 'This path does not exist.'
fi
⁤
if ! [[ -d "${line#'location_of_folder='}" ]]
then
echo 'This path exists.'
else
echo 'This path does not exist.'
fi
However both say the path is nonexistant, which is indeed not true.
And yes, inside of the text file I'm reading from looks like:
location_of_folder=~/Desktop/folder\ with\ spaces
Using bash 3.2.57(1)-release under OSX El Capitan 10.11.6.
Thank you.
This isn't really an answer, but comments are hard to format. There are several issues here, and the sequence below demonstrates some of them. Note that there are blatant bad practices here (do not use eval, but that's essentially what you need if you want to expand that ~ to a path).
$ cat input
location_of_folder=~/Desktop/directory\ with\ spaces
location_of_folder=$HOME/Desktop/directory\ with\ spaces
$ while IFS== read -r name path; do if eval "test -d $path"; then echo "$path" exists; else echo "$path" does not exist; fi; done < input
~/Desktop/directory\ with\ spaces exists
$HOME/Desktop/directory\ with\ spaces exists
$ while IFS== read name path; do if test -d "$path"; then echo "$path" exists; else echo "$path" does not exist; fi; done < input
~/Desktop/directory with spaces does not exist
$HOME/Desktop/directory with spaces does not exist
$ while IFS== read name path; do if eval test -d "$path"; then echo "$path" exists; else echo "$path" does not exist; fi; done < input
bash: test: too many arguments
~/Desktop/directory with spaces does not exist
bash: test: too many arguments
$HOME/Desktop/directory with spaces does not exist
$ while IFS== read name path; do if eval "test -d \"$path\""; then echo "$path" exists; else echo "$path" does not exist; fi; done < input
~/Desktop/directory with spaces does not exist
$HOME/Desktop/directory with spaces exists
Well, this is sort of an answer I guess, since the first line appears to give you what you want. But using eval just to expand ~ is a terrible idea.

How to get values back from an external function that requires interactivity?

One of the routines I frequently use is a check for valid arguments passed when invoking scripts. Ideally, I'd like to make these, and other, similar, routines external functions that I could call from any script, for handling these more trivial processes. But, I'm having trouble retrieving the values I need from said function(s), without making the process more complicated.
I have tried using command substitution (e.g., echoing the output of the external function into a variable name local to the calling script), which seems to at least work with simpler functions. However, working with this file checking function, requires the read command in a loop, and, thus, user interactivity, which causes the script to hang when trying to resolve the variable that function call is stored in:
#!/bin/bash
# This is a simple function I want to call from other scripts.
exist(){
# If the first parameter passed is not a directory, then the input is
#+ invalid.
if [ ! -d "$1" ]; then
# Rename $1, so we can manipulate its value.
userDir="$1"
# Ask the user for new input while his input is invalid.
while [ ! -d "$userDir" ]; do
echo "\"$userDir\" does not exist."
echo "Enter the path to the directory: "
read userDir
# Convert any tildes in the variable b/c the shell didn't get to
#+ perform expansion.
userDir=`echo "$userDir" | sed "s|~|$HOME|"`
done
fi
}
exist "$1"
How can I retrieve the value of userDir in the calling script without adding (much) complexity?
You can have the exist function interact with the user over stderr and still capture the variable with command substitution. Let's take a simplified example:
exist() { read -u2 -p "Enter dir: " dir; echo "$dir"; }
The option -u2 tells read to use file descriptor 2 (stderr) for interacting with the user. This will continue to work even if stdout has been redirected via command substitution. The option -p "Enter dir: " allows read to set the prompt and capture the user input in one command.
As an example of how it works:
$ d=$(exist)
Enter dir: SomeDirectory
$ echo "$d"
SomeDirectory
Complete example
exist() {
local dir="$1"
while [ ! -d "$dir" ]; do
echo "'$dir' is not a directory." >&2
read -u2 -p "Enter the path to the directory: " dir
dir="${dir/\~/$HOME}"
done
echo "$dir"
}
As an example of this in use:
$ d=$(exist /asdf)
'/asdf' is not a directory.
Enter the path to the directory: /tmp
$ echo "new directory=$d"
new directory=/tmp
Notes:
There is no need for an if statement and a while loop. The while is sufficient on its own.
Single quotes can be put in double-quoted strings without escapes. So, if we write the error message as "'$dir' is not a directory.", escapes are not needed.
All shell variables should be double-quoted unless one wants them to be subject to word splitting and pathname expansion.
Right off the bat I'd say you can 'echo' to the user on stderr and echo your intended answer on stdout.
I had to rearrange a bit to get it working, but this is tested:
exist(){
# If the first parameter passed is not a directory, then the input is
#+ invalid.
userDir="$1"
if [ ! -d "$userDir" ]; then
# Ask the user for new input while his input is invalid.
while [ ! -d "$userDir" ]; do
>&2 echo "\"$userDir\" does not exist."
>&2 echo "Enter the path to the directory: "
read userDir
done
else
>&2 echo "'$1' is indeed a directory"
fi
echo "$userDir"
}
When I tested, I saved that to a file called exist.inc.func
Then I wrote another script that uses it like this:
#!/bin/sh
source ./exist.inc.func
#Should work with no input:
varInCallingProg=$(exist /root)
echo "Got back $varInCallingProg"
#Should work after you correct it interactively:
varInCallingProg2=$(exist /probablyNotAdirOnYourSystem )
echo "Got back $varInCallingProg2"

How to pass a list of files with a particular extension in a for loop in bash

First of all, hi to everyone, that's my first post here.
I swear I have checked the site for similar questions to avoid the "double post about same argument" issue but none of them answered exactly to my question.
The problem is that in the code below I always get the "There are no files with this extension" message when I call the script passing it an extension as first argument.
#!/bin/bash
if [ "$1" ];
then
file=*."$1";
if [ -f "$file" ];
then
for i in "$file";
[...do something with the each file using "$i" like echo "$i"]
else
echo "There are no files with this extension";
fi;
else
echo "You have to pass an extension"
fi;
I tried using the double parenthesis, using and not using the quotes in the nested if, using *."$1" directly in the if, but none of this solution worked.
One problem is that you're not quoting a variable when you first assign a value to file. In this statement:
file=*."$1";
The * will be interpreted by the shell, so for example if you passed in .py on the command line, file might end up with the value file1.py file2.py, which will throw off your file existence test later on.
Another problem, as #sideshowbarker points out, is that you can't use wildcards with the [ -f ... ].
Another variable quoting issue is that quoting inhibits wildcard expansion, such that even without the file existence test, if $file is, e.g., *.txt, then this:
for x in "$file"; do ...
Will loop over a single argument with the literal value *.txt, while this:
for x in $file; do ...
Will loop over all files that end with a .txt extension (unless there are none, in which case it will loop once with $x set to the literal value *.txt).
Typically, you would write your script to expect a list of arguments, and allow the user to call it like myscript *.txt...that is, leave wildcard handling to the interactive shell, and just let your script process a list of arguments. Then it becomes simply:
for i in "$#"; do
echo do something with this file named "$x"
done
If you really want to handle the wildcard expansion in your script, something like this might work:
#!/bin/bash
if [ "$1" ];
then
ext="$1"
for file in *.$ext; do
[ -f "$file" ] || continue
echo $file
done
else
echo "You have to pass an extension"
fi;
The statement [ -f "$file" ] || continue is necessary there because of the case I mentioned earlier: if there are no files, the loop will still execute once with the literal expansion of *.$ext.

Bash scripting checking content of a variable containing a directory path

I am trying to write a simple bash script on Ubuntu 12.10 which stores the result of the pwd command in a variable and then checks the value of that variable in an if command to see if it matches a particular string. But I keep getting an error because it treats the content of that variable as a directory itself and keeps giving the error "No such file or directory"
The program is as below:
myvar=$(pwd)
if [$myvar -eq /home/vicky] #fails this check as variable myvar contains /home/vicky
then
echo correct
else
echo incorrect
fi
Any help would be appreciated
The proper form for that is
myvar=$(pwd)
if [ "$myvar" = /home/vicky ] ## Need spaces and use `=`.
And since you're in bash, you don't need to use pwd. Just use $PWD. And also, it's preferrable to use [[ ]] since variables are not just to word splitting and pathname expansion when in it.
myvar=$PWD
if [[ $myvar == /home/vicky ]]
Or simply
if [[ $PWD == /home/vicky ]]

shell script working fine on one server but not on another

the following script is working fine on one server but on the other it gives an error
#!/bin/bash
processLine(){
line="$#" # get the complete first line which is the complete script path
name_of_file=$(basename "$line" ".php") # seperate from the path the name of file excluding extension
ps aux | grep -v grep | grep -q "$line" || ( nohup php -f "$line" > /var/log/iphorex/$name_of_file.log & )
}
FILE=""
if [ "$1" == "" ]; then
FILE="/var/www/iphorex/live/infi_script.txt"
else
FILE="$1"
# make sure file exist and readable
if [ ! -f $FILE ]; then
echo "$FILE : does not exists. Script will terminate now."
exit 1
elif [ ! -r $FILE ]; then
echo "$FILE: can not be read. Script will terminate now."
exit 2
fi
fi
# read $FILE using the file descriptors
# $ifs is a shell variable. Varies from version to version. known as internal file seperator.
# Set loop separator to end of line
BACKUPIFS=$IFS
#use a temp. variable such that $ifs can be restored later.
IFS=$(echo -en "\n")
exec 3<&0
exec 0<"$FILE"
while read -r line
do
# use $line variable to process line in processLine() function
processLine $line
done
exec 0<&3
# restore $IFS which was used to determine what the field separators are
IFS=$BAKCUPIFS
exit 0
i am just trying to read a file containing path of various scripts and then checking whether those scripts are already running and if not running them. The file /var/www/iphorex/live/infi_script.txt is definitely present. I get the following error on my amazon server-
[: 24: unexpected operator
infinity.sh: 32: cannot open : No such file
Thanks for your helps in advance.
You should just initialize file with
FILE=${1:-/var/www/iphorex/live/infi_script.txt}
and then skip the existence check. If the file
does not exist or is not readable, the exec 0< will
fail with a reasonable error message (there's no point
in you trying to guess what the error message will be,
just let the shell report the error.)
I think the problem is that the shell on the failing server
does not like "==" in the equality test. (Many implementations
of test only accept one '=', but I thought even older bash
had a builtin that accepted two '==' so I might be way off base.)
I would simply eliminate your lines from FILE="" down to
the end of the existence check and replace them with the
assignment above, letting the shell's standard default
mechanism work for you.
Note that if you do eliminate the existence check, you'll want
to either add
set -e
near the top of the script, or add a check on the exec:
exec 0<"$FILE" || exit 1
so that the script does not continue if the file is not usable.
For bash (and ksh and others), you want [[ "$x" == "$y" ]] with double brackets. That uses the built-in expression handling. A single bracket calls out to the test executable which is probably barfing on the ==.
Also, you can use [[ -z "$x" ]] to test for zero-length strings, instead of comparing to the empty string. See "CONDITIONAL EXPRESSIONS" in your bash manual.

Resources