double bracket does not work for -d and -f commands - bash

if [[ -d "$HOME/test_bash/$KIT.stat_$KIT" ]; then
echo $KIT
else
mkdir $KIT.stat_$KIT
fi
if [[ "14"=="14" ]]; then
echo "FOO"
fi
The first if statement with [[ -d does not work, but the second if statement "14"=="14" does work when I use the bash test.sh command. However, if I replace the first if statement '[[' with a single bracket '[', it works. Any idea why this is the case?

You only have a single closing ] on your first line.

Related

Shell If else with regex

I am using shell to simple String regex match. Here is my shell
#!/bin/sh
MSG="ANK"
PATTERN="([A-Z]{3,5}[-][0-9]{2,5})"
if [ "$MSG" =~ "$PATTERN" ]; then
echo "MATCHED";
else
echo "not";
fi
It is giving error
abc.sh: 6: [: ANK: unexpected operator
How should I fix this?
Making the changes proposed by several contributors in the comments yields:
#!/bin/bash
MSG="ANK"
PATTERN="([A-Z]{3,5}[-][0-9]{2,5})"
if [[ "$MSG" =~ $PATTERN ]]; then
echo "MATCHED";
else
echo "not";
fi
Note the change to bash, the change to [[ and removal of the quotation marks around $PATTERN.

Bash scripting: syntax error near unexpected token `done'

I am new to bash scripting and I have to create this script that takes 3 directories as arguments and copies in the third one all the files in the first one that are NOT in the second one.
I did it like this:
#!/bin/bash
if [ -d $1 && -d $2 && -d $3 ]; then
for FILE in [ ls $1 ]; do
if ! [ find $2 -name $FILE ]; then
cp $FILE $3
done
else echo "Error: one or more directories are not present"
fi
The error I get when I try to execute it is: "line 7: syntax error near unexpected token `done' "
I don't really know how to make it work!
Also even if I'm using #!/bin/bash I still have to explicitly call bash when trying to execute, otherwise it says that executing is not permitted, anybody knows why?
Thanks in advance :)
Couple of suggestions :
No harm double quoting variables
cp "$FILE" "$3" # prevents wordsplitting, helps you filenames with spaces
for statement fails for the fundamental reason -bad syntax- it should've been:
for FILE in ls "$1";
But then, never parse ls output. Check [ this ].
for FILE in ls "$1"; #drastic
Instead of the for-loop in step2 use a find-while-read combination:
find "$1" -type f -print0 | while read -rd'' filename #-type f for files
do
#something with $filename
done
Use lowercase variable names for your script as uppercase variables are reserved for the system. Check [this].
Use tools like [ shellcheck ] to improve script quality.
Edit
Since you have mentioned the input directories contain only files, my alternative approach would be
[[ -d "$1" && -d "$2" && -d "$3" ]] && for filename in "$1"/*
do
[ ! -e "$2/${filename##*/}" ] && cp "$filename" "$3"
done
If you are baffled by ${filename##*/} check [ shell parameter expansion ].
Sidenote: In linux, although discouraged it not uncommon to have non-standard filenames like file name.
Courtesy: #chepner & #mklement0 for their comments that greatly improved this answer :)
Your script:
if ...; then
for ...; do
if ...; then
...
done
else
...
fi
Fixed structure:
if ...; then
for ...; do
if ...; then
...
fi # <-- missing
done
else
...
fi
If you want the script executable, then make it so:
$ chmod +x script.sh
Notice that you also have other problems in you script. It is better written as
dir1="$1"
dir2="$2"
dir3="$3"
for f in "$dir1"/*; do
if [ ! -f "$dir2/$(basename "$f")" ]; then
cp "$f" "$dir3"
fi
done
this is not totally correct:
for FILE in $(ls $1); do
< whatever you do here >
done
There is a big problem with that loop if in that folder there is a filename like this: 'I am a filename with spaces.txt'.
Instead of that loop try this:
for FILE in "$1"/*; do
echo "$FILE"
done
Also you have to close every if statement with fi.
Another thing, if you are using BASH ( #!/usr/bin/env bash ), it is highly recommended to use double brackets in your test conditions:
if [[ test ]]; then
...
fi
For example:
$ a='foo bar'
$ if [[ $a == 'foo bar' ]]; then
> echo "it's ok"
> fi
it's ok
However, this:
$ if [ $a == 'foo bar' ]; then
> echo "it's ok";
> fi
bash: [: too many arguments
You've forgot fi after the innermost if.
Additionally, neither square brackets nor find do work this way. This one does what your script (as it is now) is intended to on my PC:
#!/bin/bash
if [[ -d "$1" && -d "$2" && -d "$3" ]] ; then
ls -1 "$1" | while read FILE ; do
ls "$2/$FILE" >/dev/null 2>&1 || cp "$1/$FILE" "$3"
done
else echo "Error: one or more directories are not present"
fi
Note that after a single run, when $2 and $3 refer to different directories, those files are still not present in $2, so next time you run the script they will be copied once more despite they already are present in $3.

Multiple `if` statements in bash script

I'm trying to write a short bash script that optionally accepts arguments from the command line, or prompts for their input
if [ [ -z "$message" ] && [ -z "$predefined" ] ] ; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" ]; then
if [ -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
fi
If neither message nor predefined has been passed in as command line arguments, then the code should prompt for a value for message; otherwise if predefined has been passed in as a command line argument, then the script should test for the existence of a file of that name and only continue if the file does exist
But I'm getting the following error
[: -z: binary operator expected
at the first of those if tests
Any help in explaining what's wrong with my syntax for that first if statement? Or providing an alternative syntax to achieve the same objectives.
The first if is not well-formed. This would work:
if [ -z "$message" ] && [ -z "$predefined" ]; then
or this:
if test -z "$message" && test -z "$predefined"; then
or this bash-specific, easy but dirty way:
if [[ -z "$message" ]] && [[ -z "$predefined" ]]; then
or this bash-specific proper way:
if [[ -z $message && -z $predefined ]]; then
In this last version the double-quotes are unnecessary, not a typo.
Thanks #mklement0 for the corrections in the bash-specific style, and for this final comment:
I should note that there's one case where double-quoting is still a must inside [[ ... ]], namely if you want a variable reference on the right side of a string comparison (==) to be treated as a literal:
v='[a]'
[[ $v == $v ]] # FALSE!
[[ $v == "$v" ]] # true
Without double-quoting, the right-hand side is interpreted as a pattern. Some people advocate always double-quoting variable references so as not to have to remember such subtleties. That said (from bash 3.2 on), you must NOT double-quote the right operand when regex matching with =~
test expression1 -a expression2
is true if both expressions are true.
test expression1 -o expression2
is true if either or both expressions are true.
if [ -z "$message" -a -z "$predefined" ]; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" -a -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
This was able to combine 4 test into 2 while also getting rid of one nested if expression; then ; fi

Comparing strings in if and using or

This code is not working, but I don't know what's wrong.
If I only use single brackets the string isn't compared right.
#!/bin/bash
forceupdate=false
currentVersion=520-19
latestVersion=520-19
if [[ "$latestVersion" > "$currentVersion" -o forceupdate ]]
then
echo -e "\nupdate!\n"
else
echo -e "\nno update!\n"
fi
$forceupdate inside brackets will actually be true, because it's not going to execute the false executable, but it will see a non-empty string.
if [[ "$latestVersion" > "$currentVersion" ]] || $forceupdate

Escaping in test comparisons

In the following, I would like check if a given variable name is set:
$ set hello
$ echo $1
hello
$ echo $hello
$ [[ -z \$$1 ]] && echo true || echo false
false
Since $hello is unset, I would expect the test to return true. What's wrong here? I would assume I am escaping the dollar incorrectly.
TYIA
You are testing if \$$1 is empty. Since it begins with a $, it is not empty. In fact, \$$1 expands to the string $hello.
You need to tell the shell that you want to treat the value of $1 as the name of a parameter to expand.
With bash: [[ -z ${!1} ]]
With zsh: [[ -z ${(P)1} ]]
With ksh: tmp=$1; typeset -n tmp; [[ -z $tmp ]]
Portably: eval "tmp=\$$1"; [ -z "$tmp" ]
(Note that these will treat unset and empty identically, which is usually the right thing.)

Resources