Bash - if and for statements - bash

I am little unfamiliar with the 'if...then...fi' and the 'for' statements syntax.
Could anyone explain what the "$2/$fn" and "/etc/*release" in the code snippets below mean?...specifically on the use of the forward slash....and the asterisk...
if [ -f "$filename" ]; then
if [ ! -f "$2/$fn" ]; then
echo "$fn is missing from $2"
missing=$((missing + 1))
fi
fi
and
function system_info
{
if ls /etc/*release 1>/dev/null 2>&1; then
echo "<h2>System release info</h2>"
echo "<pre>"
for i in /etc/*release; do
# Since we can't be sure of the
# length of the file, only
# display the first line.
head -n 1 $i
done
uname -orp
echo "</pre>"
fi
} # end of system_info
...thx for the help...

/etc/*release : here the * will match any number of any characters, so any thing /etc/0release , /etc/asdfasdfr_release etc will be matched. Simply stated, it defined all the files in the /etc/ directory which ends with the string release.
The $2 is the 2nd commandline argument to the shell script, and $fn is some other shell variable. The "$2/$fn" after the variable substitutions will make a string, and the [ -f "$2/$fn" ] will test if the string formed after the substitution forms a path to a regular file which is specified by the -f switch. If it is a regular file then the body of if is executed.
In the for loop the loop will loop for all the files ending with the string release in the directory /etc (the path). At each iteration i will contain the next such file name, and for each iteration the first 1 line of the file is displayed with the head command by getting the file name from variable i within the body.
It is better to check the manual man bash and for if condition check man test . Here is a good resource: http://tldp.org/LDP/Bash-Beginners-Guide/html/

The forward slash is the path separator, and the * is a file glob character. $2/$fn is a path where $2 specifies the directory and $fn is the filename. /etc/*release expands to the space separated list of all the files in /etc whose name ends in "release"

Dollar sign marks variable. The "-f" operator means "file exsists".
So,
[ -f "$filename" ]
checks if there is file named the same as value contained in $filename variable.
Simmilar, if we assume that $2 = "some_folder", and $fn = "some_file", expression
[ ! -f "$2/$fn" ]
returns true if file some_folder/some_file doesn't exsist.
Now, about asterisk - it marks "zero or more of any character(s)". So, expression:
for i in /etc/*release; do
will iterate trough all folders named by that pattern, for example:
/etc/release, /etc/666release, /etc/wtf_release...
I hope this helps.

Related

How to check filetype in if statement bash using wildecard and -f

subjects_list=$(ls -l /Volumes/Backup_Plus/PPMI_10 | awk '{ print $NF }')
filepath="/Volumes/Backup_Plus/PPMI_10/$subjects/*/*/S*/"
for subjects in $subjects_list; do
if [[ -f "${filepath}/*.bval" && -f "${filepath}/*.bvec" && -f "${filepath}/*.json" && -f "${filepath}/*.nii.gz" ]]; then
echo "${subjects}" >> /Volumes/Backup_Plus/PPMI_10/keep_subjects.txt
else
echo "${subjects}" >> /Volumes/Backup_Plus/PPMI_10/not_keep_subjects.txt
fi
done
problem is supposedly in the if statement, I tried this...
bvalfile = (*.bval)
bvecfile =(*.bvec)
jsonfile =(*.json)
niigzfile =(*.nii.gz)
if [[ -f "$bvalfile" && -f "$bvecfile" && -f "$jsonfile" && -f "$niigzfile" ]]; then
however that didn't work. Any help with syntax or errors or does it need to be changed completely. Trying to separate the files that have .^file types from those that don't by making two lists.
thanks
You're assigning filepath outside the for-subject loop but using the unset variable $subjects in it. You want to move that inside the loop.
Double-quoted wildcards aren't expanded, so both $filepath and your -f test will be looking for filenames with literal asterisks in them.
-f only works on a single file, so even if you fix the quotes, you'll have a syntax error if there's more than one file matching the pattern.
So I think what you want is something like this:
# note: array assignment -
# shell does the wildcard expansion, no ls required
prefix_list=( /Volumes/Backup_Plus/PPMI_10/* )
# and array expansion
for prefix in "${prefix_list[#]}"; do
# the subject is just the last component of the path
subject=${prefix##*/}
# start by assuming we're keeping this one
decision=keep
# in case filepath pattern matches more than one directory, loop over them
for filepath in "$prefix"/*/*/S*/; do
# if any of the files don't exist, switch to not keeping it
for file in "$filepath"/{*.bval,*.bvec,*.json,*.nii.gz}; do
if [[ ! -f "$file" ]]; then
decision=not_keep
# we have our answer and can stop looping now
break 2
fi
done
done
# now append to the correct list
printf '%s\n' "$subject" >>"/Volumes/Backup_Plus/PPMI_10/${decision}_subjects.txt"
done

Parse ${} placeholder into absolute path in shell script

I have a app.properties file something like below
Base.dir="/user/test/application"
Result.dir="${base.dir}/result"
and i've create bash script to parse above properties
function readConfigFile()
{
(grep -E "^${2}=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d '=' -f 2-;
}
function setConfigFile()
{
sourceFile=${1}
}
function configGet()
{
if [ ! -z $sourceFile ]; then
val="$(readConfigFile $sourceFile "${1}")";
if [ "${val}" = "__UNDEFINED__" ]; then
echo "${1} value not exist"
# return empty string
printf -- "%s" "";
fi
printf -- "%s" "${val}";
else
echo "config file not exist"
# return empty string
printf -- "%s" "";
fi
}
and the way i call above parser is something like below
$Result_dir=$(configGet Result.dir)
however, i cant really translate placeholder ${} into base_dir
and i got following error
ls $Result_dir
ls: cannot access ${Base_dir}/result: No such file or directory
Is there any way that i can translate ${Base.dir} into /user/test/application?
I guess you're not going to be able to substitute ${base.dir} (btw shouldn't it be ${Base.dir}?) the way you were hoping mainly because, as far as I know, dots are not allowed in variable names in bash.
What you could do is manually substitute the ${base.dir} part with the corresponding path using bash's substitution syntax. For example:
setConfigFile 'app.properties'
Result_dir_raw=$(configGet Result.dir)
Result_dir=${Result_dir_raw/'${base.dir}'/$(configGet Base.dir)}
echo ${Result_dir}
I say "manually" because you still specify in your source code that the pattern you want to replace is ${base.dir} which I'm guessing isn't what you wanted.
Now if you run this you'll see that the ${Result_dir} variable evaluates to ""/user/test/application"/result" which obviously isn't a path, and this is because you're surrounding the paths in app.properties with double quotes, so you either need to get rid of them in your readConfigFile function or lose them altogether in your config file, which to me makes more sense.
Why have you a . in your variable name, which is not allowed in bash:
$ Base.dir="/user/test/application"
-bash: Base.dir=/user/test/application: No such file or directory
$ Base_dir="/user/test/application"
$
So, why do you get No such file or directory? Here is an explanation:
Create a file called Base.dir=gash.sh, yes, that's a legal filename
$ echo 'echo Hello World' > Base.dir=gash.sh
Make the file executable:
$ PATH=$PATH:.
$ chmod u+x Base.dir=gash.sh
Now type the command:
$ Base.dir="gash.sh"
Hello World
Use an underscore, not a dot. By the Way, ksh Korn shell not only allows the dot, it has a special meaning, it is a compound variable.

ls $FOLDER_PATH with space in $FOLDER_PATH: No such file or directory

I am trying to get the filename in a folder with only one file in it.
FYI: The $FOLDER_TMP contains a space in it, that is why I use printf
function nameofkeyfile(){
FOLDER_TMP="${PWD%/*/*}/folder/"
FOLDER=$(printf %q "${FOLDER_TMP}")
FILENAME=ls "$FOLDER" # Error: No such file or directory
# or this: FILENAME=$(ls "$FOLDER") # Error: No such file or directory
FNAME=`basename $FILENAME`
}
The problem is the line:
FILENAME=ls "$FOLDER" # Error: No such file or directory
Do you know why - and yes the folder is there?
And if I echo the $FOLDER it gives me the right folder.
I am trying to get the filename in a folder with only one file in it.
You definitely have the wrong approach.
Instead, consider using globbing like so:
The assignment
fname=( "${PWD%/*/*}"/folder/* )
will populate the array fname will the expansion of the given glob: that is, all files in the directory "${PWD%/*/*}"/folder/, if any. If there are no files at all, your array will contain the glob, verbatim.
Hence, a more robust approach is the following:
nameofkeyfile() {
fname=( "${PWD%/*/*}"/folder/* )
# Now check that there's at most one element in the array
if (( ${#fname[#]} > 1 )); then
echo "Oh no, there are too many files in your folder"
return 1
fi
# Now check that there is a file
if [[ ! -f ${fname[0]} ]]; then
echo "Oh no, there are no files in your folder"
return 1
fi
# Here, all is good!
echo "Your file is: $fname"
}
This uses Bash (named) arrays. If you want the function to be POSIX-compliant, it's rather straightforward since POSIX shells have an unnamed array (the positional parameters):
# POSIX-compliant version
nameofkeyfile() {
set -- "${PWD%/*/*}"/folder/*
# Now check that there's at most one element in the array
if [ "$#" -gt 1 ]; then
echo "Oh no, there are too many files in your folder"
return 1
fi
# Now check that there is a file
if [ ! -f "$1" ]; then
echo "Oh no, there are no files in your folder"
return 1
fi
# Here, all is good!
echo "Your file is: $1, I'll store it in variable fname for you"
fname=$1
}
I didn't strip the full path from the filename, but that's really easy (don't use basename for that!):1
fname=${fname##*/}
More precisely: in the Bash version, you'd use:
fname=${fname[0]##*/}
and in the POSIX version you'd use:
fname=${1##*/}
1there's a catch when using parameter expansions to get the basename, it's the case of /. But it seems you won't be in this case, so it's all safe!
To store the output ls "$FOLDER" in a variable, put it in a sub-shell:
FILENAME=$(ls "$FOLDER")
Another problem is the printf.
It adds escaping backslashes in the string,
and when you try to list the directory in the next step,
those backslashes are used literally by the shell.
So drop the printf:
function nameofkeyfile() {
FOLDER="${PWD%/*/*}/folder/"
FILENAME=$(ls "$FOLDER")
FNAME=$(basename $FILENAME)
}
Lastly, it's better to use $(...) than `...`:

Shell Script: Concatenate string while interating

I am writing a shell script that iterates over directory content and searches for pdf files and creates a string listing all pdf files in it e.g. "pdffile1.pdf pdffile2.pdf pdffile3.pdf".
pdffiles=""
#get files in pdf directory
for filename in $1/*; do
fn=$(basename "$filename")
#check if file exist
if [ -f "$filename" ]; then
#grab only pdf files
if [ ${filename: -4} == ".pdf" ]; then
pdffiles = $filename $pdffiles
fi
fi
done
The thing is this code pdffiles = $filename $pdffiles is wrong and shell script outputs following error message ./mergepdfs.sh: line 39: pdffiles: command not found.
What is wrong with that line?
Don't use spaces around '=' when assigning:
x = 1 # execution of x with arguments '=' and '1'
x=1 # setting shell variable x
Why not simply:
pdffiles=$1/*.pdf
If you like to get them in array form:
pdffiles=($1/*.pdf)
Output all:
echo ${pdffiles[*]}
Output size:
echo ${#pdffiles[*]}
Output a single name:
echo ${pdffiles[4]}
I think you don't need space around =. This must be a correct line:
pdffiles=$filename' '$pdffiles

What is the meaning of ${0%/*} in a bash script?

I am trying to understand a test script, which includes the following segment:
SCRIPT_PATH=${0%/*}
if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then
cd $SCRIPT_PATH
fi
What does the ${0%/*} stand for? Thanks
It is called Parameter Expansion. Take a look at this page and the rest of the site.
What ${0%/*} does is, it expands the value contained within the argument 0 (which is the path that called the script) after removing the string /* suffix from the end of it.
So, $0 is the same as ${0} which is like any other argument, eg. $1 which you can write as ${1}. As I said $0 is special, as it's not a real argument, it's always there and represents name of script. Parameter Expansion works within the { } -- curly braces, and % is one type of Parameter Expansion.
%/* matches the last occurrence of / and removes anything (* means anything) after that character. Take a look at this simple example:
$ var="foo/bar/baz"
$ echo "$var"
foo/bar/baz
$ echo "${var}"
foo/bar/baz
$ echo "${var%/*}"
foo/bar

Resources