Frustraiting How are there too many arguments in this if statement - if-statement

if [ $? -ne 0 ]; then
STATUS=$(cat $LOG.$i)
if [ $STATUS != "$i-DOWN!" ]; then
echo "$(date): ping failed, $i host is down!" |
mail -s "$(date) $i host is down!" $EMAIL
fi
echo "$(date) $i-DOWN!" > $LOG.$i
else
STATUS=$(cat $LOG.$i)
**------->>>**if [ $STATUS != "$i-UP!" ]; then
echo "$(date): ping OK, $i host is up!" |
mail -s "$(date) $i host is up!" $EMAIL
fi
echo "$(date) $i-UP!" > $LOG.$i
fi
I keep getting the error "Too many arguments" for that specific line with teh arrow pointing to it.. but I have the exact same line above it and do not get the error...
This is frustrating anyone have any idea why?

My guess is that $STATUS contains spaces, try to wrap the expression in quotes:
if [ "$STATUS" != "$i-UP!" ]
The line above was not evaluated so it can't ever trigger an error. Note that shell scripts are not compiled and every line is evaluated only if it's reached at runtime.

Related

Check if a string contains valid pattern in Bash

I have a file a.txt contains a string like:
Axxx-Bxxxx
Rules for checking if it is valid or not include:
length is 10 characters.
x here is digits only.
Then, I try to check with:
#!/bin/bash
exp_len=10;
file=a.txt;
msg="checking string";
tmp="File not exist";
echo $msg;
if[ -f $file];then
tmp=$(cat $file);
if[[${#tmp} != $exp_len ]];then
msg="invalid length";
elif [[ $tmp =~ ^[A[0-9]{3}-B[0-9]{4}]$]];then
msg="valid";
else
msg="invalid";
fi
else
msg="file not exist";
fi
echo $msg;
But in valid case it doesn't work...
Is there someone help to correct me?
Thanks :)
Other than the regex fix, your code can be refactored as well, moreover there are syntax issues as well. Consider this code:
file="a.txt"
msg="checking string"
tmp="File not exist"
echo "$msg"
if [[ -f $file ]]; then
s="$(<$file)"
if [[ $s =~ ^A[0-9]{3}-B[0-9]{4}$ ]]; then
msg="valid"
else
msg="invalid"
fi
else
msg="file not exist"
fi
echo "$msg"
Changes are:
Remove unnecessary cat
Use [[ ... ]] when using bash
Spaces inside [[ ... ]] are required (your code was missing them)
There is no need to check length of 10 as regex will make sure that part as well
As mentioned in comments earlier correct regex should be ^A[0-9]{3}-B[0-9]{4}$ or ^A[[:digit:]]{3}-B[[:digit:]]{4}$
Note that a regex like ^[A[0-9]{3}-B[0-9]{4}]$ matches
^ - start of string
[A[0-9]{3} - three occurrences of A, [ or a digit
-B - a -B string
[0-9]{4} - four digits
] - a ] char
$ - end of string.
So, it matches strings like [A[-B1234], [[[-B1939], etc.
Your regex checking line must look like
if [[ $tmp =~ ^A[0-9]{3}-B[0-9]{4}$ ]];then
See the online demo:
#!/bin/bash
tmp="A123-B1234";
if [[ $tmp =~ ^A[0-9]{3}-B[0-9]{4}$ ]];then
msg="valid";
else
msg="invalid";
fi
echo $msg;
Output:
valid
Using just grep might be easier:
$ echo A123-B1234 > valid.txt
$ echo 123 > invalid.txt
$ grep -Pq 'A\d{3}-B\d{4}' valid.txt && echo valid || echo invalid
valid
$ grep -Pq 'A\d{3}-B\d{4}' invalid.txt && echo valid || echo invalid
invalid
With your shown samples and attempts, please try following code also.
#!/bin/bash
exp_len=10;
file=a.txt;
msg="checking string";
tmp="File not exist";
if [[ -f "$file" ]]
then
echo "File named $file is existing.."
awk '/^A[0-9]{3}-B[0-9]{4}$/{print "valid";next} {print "invalid"}' "$file"
else
echo "Please do check File named $file is not existing, exiting from script now..."
exit 1;
fi
OR In case you want to check if line in your Input_file should be 10 characters long(by seeing OP's attempted code's exp_len shell variable) then try following code, where an additional condition is also added in awk code.
#!/bin/bash
exp_len=10;
file=a.txt;
msg="checking string";
tmp="File not exist";
if [[ -f "$file" ]]
then
echo "File named $file is existing.."
awk -v len="$exp_len" 'length($0) == len && /^A[0-9]{3}-B[0-9]{4}$/{print "valid";next} {print "invalid"}' "$file"
else
echo "Please do check File named $file is not existing, exiting from script now..."
exit 1;
fi
NOTE: I am using here -f flag to test if file is existing or not, you can change it to -s eg: -s "$file" in case you want to check file is present and is of NOT NULL size.

How to structure a compound conditional with several tests (at least 1 of which is regex)

I searched for this but haven't found an answer to this particular situation. I'm familiar with file tests in shells and with using the [[ ]] syntax to perform regex matching.
Is there a way to combine these two operations in a compound conditional that doesn't require multiple nested ifs?
So far I've tried the following (...and other much crazier variations):
if [ -e ~/.profile -a $0 =~ bash ]; then echo yes ; fi
if [ -e ~/.profile -a ( $0 =~ bash ) ]; then echo yes ; fi
if [ -e ~/.profile -a [ $0 =~ bash ] ]; then echo yes ; fi
if [[ -e ~/.profile -a $0 =~ bash ]]; then echo yes ; fi
if [[ ( -e ~/.profile ) -a ( $0 =~ bash ) ]]; then echo yes ; fi
if [ -e ~/.profile -a $0 =~ bash ]; then echo yes; fi
if [ -e ~/.profile -a $( [ $0 =~ bash ] ) ]; then echo yes; fi
if [ -e ~/.profile -a [[ $0 =~ bash ]] ]; then echo yes; fi
if [ -e ~/.profile -a $([[ $0 =~ bash ]]) ]; then echo yes; fi
-a is treated as an AND when using single brackets, eg:
$ [ 3 -gt 1 -a 2 -lt 3 ] && echo 'true'
true
For double brackets you want to use &&, eg:
$ [[ 3 -gt 1 && 2 -lt 3 ]] && echo 'true'
true
Alternatively you can && to separate tests regardless of whether you're using single or double brackets, eg:
$ [ 3 -gt 1 ] && [ 2 -lt 3 ] && echo 'true'
true
$ [[ 3 -gt 1 ]] && [[ 2 -lt 3 ]] && echo 'true'
true
$ [ 3 -gt 1 ] && [[ 2 -lt 3 ]] && echo 'true'
true
NOTE: same rules apply for -o vs || (aka OR)
Apparently, when you want to represent a LOGICAL AND between these two statements, you must use && instead of -a (which the shell interprets as "does this file exist" file test in double brackets). Also, for the regex to work, the statement must be within [[ ]]. What was unknown to me at the time is that even though -a changes its meaning in double brackets, the -e -w -r and other file tests don't change their functionality (e.g. it's the same for single or double brackets).
if [[ -w ~/.bash_profile && $0 =~ bash ]]; then ( echo 1 ; echo 2 ) >> .bash_profile
elif [[ -w ~/.profile && <someothercondition> ]]; then
( echo 3
echo 4
echo 5
) >> .profile
fi

Bash script checking command line parameters

I am still on the learning path of bash, shell, Linux, regex etc. Today I share this bash shell script that I have programmed and that I want to use as a "module" in future scripts. It thoroughly tests the validity of a set of command line parameters. I would like to ask the experienced adepts for advice and comments on how to better archive things in terms of syntax, different approaches or alternative commands. There were a lot that I tried differently, but I couldn't figure it out. Especially I don't like the 'case' structures. I would rather define a set of option letters like 'cmds=(h o g a m)' and then loop through that with 'for c in "${cmds[#]}"; do'. But that leads to the problem that I would have to use dynamic variable names, and I couldn't figure it out. Another problem is, that I am able to assign a boolean 'true' but I can't negate it with something like 'a=!a'. Etc. Any suggestions very welcome!
#!/bin/bash
# Usage: -p <path> -g <group> -o <owner> -m <mask> -h (help)
# Extensive option and parameter check:
expecting_option=true # alternatingly expecting option switch and parameter on command line
for i do # loop $i trough all parameters
# display help:
if [ $i = "-h" ]; then
echo "Usage:"
echo "-p <path> (default .)"
echo "-o <owner>"
echo "-g <group>"
echo "-m <permission mask> (000 - 777)"
exit
fi;
if [ "$expecting_option" = true ]; then # next arg supposed to be an option
if [[ "$i" =~ ^(.)(.*?)(.*?)$ ]]; then # retrieve up to 3 single characters
# Does it begin with '-' ?
if [ ${BASH_REMATCH[1]} != "-" ]; then
echo "ERROR: Option to begin with '-' expected at '"$i"'" >&2
exit
fi
# only one letter length for options
if [ -n "${BASH_REMATCH[3]}" ]; then
echo "ERROR: Invalid option '"$i"'. Use -h for help" >&2
exit
fi
switch=${BASH_REMATCH[2]} # save the current option switch
# has this option already been set?
# is option valid?
case $switch in
o) if [ $o ]; then
echo 'ERROR: duplicate option: -o' >&2
exit
fi;;
g) if [ $g ]; then
echo 'ERROR: duplicate option: -g' >&2
exit
fi;;
m) if [ $m ]; then
echo 'ERROR: duplicate option: -m' >&2
exit
fi;;
p) if [ $p ]; then
echo 'ERROR: duplicate option: -p' >&2
exit
fi;;
*) echo "ERROR: Invalid option '"$i"'. Use -h for help" >&2
exit;;
esac
fi
# next arg supposed to be the parameter
expecting_option=!true # it's not true, so it works. But is it 'false'?
else # this is supposed to be a parameter for the previous option switch
if [[ "$i" =~ ^\- ]]; then # starts with '-' ?
echo "ERROR: Parameter for "$switch" missing." >&2
exit
fi
case $switch in
o) # check if designated owner exists (=0):
if ! [ $(id -u "$i" > /dev/null 2>&1; echo $?) -eq 0 ]; then
echo "ERROR: user '"$i"' does not exist." >&2
exit
fi
o="$i";;
g) # check if designated group exists:
if [ -z $(getent group "$i") ]; then
echo "ERROR: group '"$i"' does not exist." >&2
exit
fi
g="$i";;
m) if ! [[ $i =~ ^[0-7][0-7][0-7]$ ]]; then
echo "ERROR: Invalid right mask '"$i"'" >&2
exit
fi
m="$i";;
p) # check if path exists
if ! [ -d "${i}" ]; then
echo "ERROR: Directory '"$i"' not found." >&2
exit
fi
p="$i";;
esac
expecting_option=true
fi
done
# last arg must be a parameter:
if [ "$expecting_option" != true ]; then
echo "ERROR: Parameter for "$switch" missing." >&2
exit
fi
# at least o, g or m must be specified:
if ! [ $g ] && ! [ $o ] && ! [ $m ] ; then
# this didn't work: if ! [ [ $g ] || [ $o ] || [ $m ] ] ; then
echo "Nothing to do. Specify at least owner, group or mask. Use -h for help."
exit
fi
# defaults: path = . owner = no change group = no change mask = no change
# set defaults:
[[ -z $p ]] && p="."
# All necessary options are given and checked:
# p defaults to . otherwise valid path
# if o is given, than the user exists
# if g is given, than the group exists
# if m is given, than the mask is valid
# at least one of o,g or m are given
# no option dupes
# no missing parameters
# ok, now let's do something:
# set group:owner + mask of whole directory tree:
if [ $g ] || [ $o ] ; then
[[ -n $g ]] && g=":"$g # use chown's column only if group change required, with or without owner
sudo find $p -type f -exec chown $o$g {} + &&
sudo find $p -type d -exec chown $o$g {} +
fi
if [ $m ]; then
sudo find $p -type f -exec chmod $m {} + &&
sudo find $p -type d -exec chmod $m {} +
fi
With 'getopts', as suggested by #Shawn in a comment, the parsing will become:
# Default values for options
g=
o=
P=.
m=
while getopts g:o:p:m: opt ; do
case "$opt" in
g) g=$OPTARG ;;
o) o=$OPTARG ;;
p) p=$OPTARG ;;
m) m=$OPTARG ;;
# Abort on illgal option
*) exit 2 ;;
esac
done
shift $((OPTIND-1))
# Rest of code goes here

incrementing list variable and then loop on it

in a sh script, I am trying to make a list of filename in a folder, and then loop on it to check if two consecutive filename respond well to "expression criteria".
in a folder I have:
file1.nii
file1_mask.nii
file2.nii
file2_mask.nii
etc ...
undefined number of files. but if filex.nii exists, it must have filex_mask.nii
in a .txt file that the user modify.
it contains:
file1.nii tab some parameter \n
file2.nii tab some parameter \n
etc ...
the script take long hours after to run, and for example, the mask files are used only after few hours.
so I want at the beginning of the .sh to check if filenames are well spelled and if any files in the .txt is present in the folder.
and in case not, stop the .sh and warn the user. not wait hours before noticing the problem.
For now I tried:
test=""
for entry in "${search_dir}"/*
do
echo "$entry"
test="${test} $entry"
done
I have then a string variable with space between filenames, but it has the folder name as well.
./search_dir/file1.nii ./search_dir/file1_mask.nii
I wanted file1.nii file1_mask.nii etc ...
and now I read my .txt file and check if the filename specified in it are in my test variable.
while read -r line
do
set -- $line
stack=$1
check=False
check2=False
for i in $test; do
echo "$stack.nii"
echo "$i"
if "${stack}.nii" == "$i";
then
check=True
fi
if "${stack}_mask.nii"=="$i";
then
check2=True
fi
done
done < "$txt_file"
but it is not working.
"$stack_mask.nii"=="$i"
doesn't seems to be the good way to compare strings
it generates the error:
"file1.nii" not found
Here is my solution for now, based on glenn answer:
errs=0
while read -r line; do
set -- $line
prefix="${1}.nii"
prefix2="${1}.nii.gz"
if [ -e ${PATH}/$prefix2 ]; then
echo "File found: ${PATH}/$prefix2" >&2
elif [ -e ${PATH}/$prefix ]; then
echo "File found: ${PATH}/$prefix" >&2
else
echo "File not found: ${PATH}/$prefix" >&2
errs=$((errs + 1))
fi
prefixmask="${1}_brain_mask.nii"
prefixmask2="${1}_brain_maskefsd.nii.gz"
if [ -e ${PATH}/$prefixmask ]; then
echo "Mask file found for ${PATH}/$prefixmask" >&2
elif [ -e ${PATH}/$prefixmask2 ]; then
echo "Mask file found for ${PATH}/$prefixmask2" >&2
else
echo "Mask file not found: ${PATH}/$prefixmask" >&2
errs=$((errs + 1))
fi
done < "$INPUT"
echo $errs
if [ $errs > 0 ]; then
echo "Errors found"
exit 3
fi
then only problem now is that it always exit, even if errs is equal to 0 and I don't know why ...
I would do this:
errs=0
for f in "$search_dir"/*.mii; do
[[ $f == *_mask.mii ]] && continue # skip the mask files
prefix=${f%.mii} # strip off the extension
if [[ ! -f "${prefix}_mask.mii" ]]; then
echo "Error: $f has no mask file" >&2
((errs++))
fi
done
if [[ $errs -gt 0 ]]; then
echo "Aborting due to errors" >&2
exit 2
fi
That should be pretty efficient, since it just loops through the files once.
Now that we see the input file:
errs=0
while read -r mii_file other_stuff; do
prefix="${mii_file%.mii}"
if [[ ! -f ./"$mii_file" ]]; then # adjust your relative path accordingly
echo "File not found: $mii_file" >&2
((errs++))
elif [[ ! -f ./"${prefix}_mask.mii" ]]; then
echo "Mask file missing for $mii_file" >&2
((errs++))
fi
done < "$txt_file"
if (( errs > 0 )); then
echo "Errors found"
exit 2
fi

shell scripting and regular expression

#!bin/bash
echo enter your password :
read password
passlength=$(echo ${#password})
if [ $passlength -le 8 ];
then
echo you entered correct password
else
echo entered password is incorrect
fi
if [[$password == [a-z]*[0-9][a-z]*]];
then
echo match found
else
echo match not found
fi
I am not getting what's wrong with this code. If I enter any string as a password, let's say hello123, it gives me an error:
hello123 : command not found
What is wrong with my script?
You can do the following to make it work cross-platforms with any the bourne shell (/bin/sh) based shell, no bash specific primitives -
echo "$password" | grep -q "[a-z]*[0-9][a-z]*"
if [ $? -eq 0 ] ;then
echo "match found"
else
echo "match not found"
fi
Also feel free to use quotes around the variable names. It will save you hours and hours worth of useless debugging. :)
Technically it should give you an error like [[hello123 : command not found.
The issue is that [[$password is not expanded how you think it is. Bash will first resolve the $password variable to what you entered (i.e. hello123). This will yield the string [[hello123 which bash will then try to invoke (and fail, as there is nothing with that name).
Simply add a space () after [[ and bash will recognise [[ as the command to run (although it is a builtin).
if [[ "$password" == [a-z]*[0-9][a-z]* ]]
then
...
The corrected script is below. The errors were:
#!/bin/bash, not #!bin/bash
To read password length, just do passlength=${#password}, not
passlength=$(echo ${#password})
Always put a space after [ or [[
#!/bin/bash
echo "enter your password :"
read password
passlength=${#password}
if [[ $passlength -le 8 ]]
then
echo "you entered correct password"
else
echo "entered password is incorrect"
fi
if [[ $password == [a-z]*[0-9][a-z]* ]]
then
echo "match found"
else
echo "match not found"
fi
In the bash [[ construct, the == operator will match glob-style patterns, and =~ will match regular expressions. See the documentation.
#!/bin/bash
read -s -p "Enter Password: " password
password_length=${#password}
if [ $password_length -lt 8 -o $password_length -gt 20 ] ;then
echo -e "Invalid password - should be between 8 and 20 characters in length.";
echo ;
else
# Check for invalid characters
case $password in
*[^a-zA-Z0-9]* )
echo -e "Password contains invalid characters.";
echo ;
;;
* )
echo "Password accepted.";
echo ;
break;
;;
esac
fi
More tuned example..
Try to replace line
if [[$password == [a-z]*[0-9][a-z]*]];
with following
if echo "$password" | grep -qs '[a-z]*[0-9][a-z]*'
HTH