Bash script takes wrong conditional path for unknown reason [duplicate] - bash

This question already has answers here:
Bash if [ -d $1] returning true for empty $1
(3 answers)
Closed 5 years ago.
I have the following bash script that should list the folders in the same directory, and let me choose a folder to move in, and then list its content.
#!/bin/bash
PS3="Scelta?"
select dest in $( command ls -aF | grep "/" ); do
if [ -d $dest ]; then
cd $dest
echo "$0 : changed to $dest"
ls
break
else
echo "$0 : wrong choice" 1>$2
fi
done
the path of the script is something like
/Users/myName/Documents/GitHub/SO/Exercise4
and this is the content of the Exercise4 dir
1/ 2/ 3/ 4/ 5/ select.sh
when I run the script it prompts me something like
1) ./
2) ../
3) 1/
4) 2/
5) 3/
6) 4/
7) 5/
Scelta? 
If I choose an option between 1 and 7 the script works, but when I input a number out of that range, instead of echoing me "wrong choice" it lists me my home directory and I can't figure out why

Why This Happens
Consider:
dest=
[ -d $dest ]
What does this do? It runs the command:
[ -d ]
What does that do? It's shorthand for:
[ -n "-d" ]
...which is to say, it checks whether -d is empty, which it isn't, so the result is true.
How To Stop It
Use More Quotes
Consider this instead:
dest=""
[ -d "$dest" ]
When run, it doesn't invoke [ -d ]; instead, it runs [ -d '' ]: The quotes prevented the expansion results from being split into a different number of strings than they started as.
When On Bash, Consider The Extended Test Form
[[ -d $dest ]]
...suppresses string-splitting and glob expansion, so it works even without quotes.

It's possible it has something to do with the fact that you're using Classic Test
[ vs [[
if [[ -d $dest ]]; then
http://wiki.bash-hackers.org/syntax/ccmd/conditional_expression#behaviour_differences_compared_to_the_builtin_test_command
Or, you might just have to put quotations around it to evaluate correctly. It might just be that 8 is true and only 0 is false:
if [ -d "$dest" ]; then
I find the latter more likely; I make it a policy to quote absolutely everything all the time in bash, it's good practice.

Related

How to use "directories are the same" in a conditional in Unix Bash Shell?

I want to compare two directories in a conditional.
if [ rsync -ai --dry-run dir1/ dir2/ ]; then
echo "Different!"
fi
However, I'm getting an error.\
./test.sh: line 44: [: too many arguments
How do I fix this error and get the conditional to echo if and only if the directories are different?
Note, rsync works as expected without the conditional.
rsync -ai --dry-run dir1/ dir2/
returns null when dir1 and dir2 are the same. And a non-null value otherwise.
What goes in [ ] is a test expression, not a command. Something like [ 5 -gt 4 ] or [ "foo" = "bar" ]. That rsync command doesn't make any sense as an expression, so you get a (not terribly clear) error.
It's possible capture the output of a command with "$( )", and test to see if that's blank with the -n test operator: if [ -n "$(rsync ...)" ], but there's actually a better way here. What goes after if is itself a command. [ is a really common command to use there, but you can use any command (or pipeline, or whatever) there. Essentially, it's "if this command succeeds, then..." So you can do this:
if rsync -ai --dry-run dir1/ dir2/ | grep -q "."; then
This feeds the output of the rsync command to grep, searching for "." (a pattern matching any character, i.e. is there any output), and the -q (quiet) option tells it not to bother printing matches, just exit with success status if there's at least one match.
[[ -n string ]] returns true if the length of string is non-zero, so you can do:
if [[ -n $(rsync -ai --dry-run dir1/ dir2/) ]]; then
echo "Different!"
fi

Bash wildcard in a makefile not working

I have this makefile:
SHELL := /bin/bash -f
working :
if [ -d ffprob_runfail ]; then echo "gotcha" ;fi
error :
if [ -d ffprob_* ]; then echo "gotcha" ;fi
Executing 'make working' in a folder where the subdirectory 'ffprob_runfail' exists echoes:
if [ -d ffprob_runfail ]; then echo "gotcha" ;fi
gotcha
Executing 'make error' in the same folder echoes:
if [ -d ffprob_* ]; then echo "gotcha" ;fi
I am not sure where this 'surprising' behavior comes from - either miscoding in make or bash syntax. I tried escaping * but did not work. Might be an issue with the syntax of [ ] bash operator? (I am quite new to bash, after 20 years of csh pain...)
Any hint appreciated.
POST EDIT:
Not only the -f option disables globbing (thanks #choroba), but also the -d operator in bash is unary, and cannot used safely with globbing, i.e. refer to Bash Shell Script: confirm the existance of one or more directories.
So this looks to be the right way (continuation of the previous makefile...):
right :
for item in ffprob_* ; do if [ -d "$$item" ] ; \
then echo "gotcha $$item";fi;done
The -f option for bash means the same as the -f option to set, namely
-f Disable file name generation (globbing).
With globbing disabled, wildcards aren't expanded.
So, why do you set the shell to bash -f? Remove the -f.

Checking the input arguments to script to be empty failed in bash script

This a small bash program that is tasked with looking through a directory and counting how many files are in the directory. It's to ignore other directories and only count the files.
Below is my bash code, which seems to fail to count the files specifically in the directory, I say this because if I remove the if statement and just increment the counter the for loop continues to iterate and prints 4 in the counter (this is including directories though). With the if statement it prints this to the console.
folder1 has files
Looking at other questions I think the expression in my if statement is right and I am getting no compilation errors for syntax or another problems.
So I just simply dumbfounded as to why it is not counting the files.
#!/bin/bash
folder=$1
if [ $1 = empty ]; then
folder=empty
counter=0
echo $folder has $counter files
exit
fi
for d in $(ls $folder); do
if [[ -f $d ]]; then
let 'counter++'
fi
done
echo $folder has $counter files
Thank you.
Your entire script could be very well simplified as below with enhancements made. Never use output of ls programmatically. It should be used only in the command-line. The -z construct allows to you assert if the parameter following it is empty or non-empty.
For looping over files, use the default glob expansion provided by the shell. Note the && is a short-hand to do a action when the left-side of the operand returned a true condition, in a way short-hand equivalent of if <condition>; then do <action>; fi
#!/usr/bin/env bash
[ -z "$1" ] && { printf 'invalid argument passed\n' >&2 ; exit 1 ; }
shopt -s nullglob
for file in "$1"/*; do
[ -f "$file" ] && ((count++))
done
printf 'folder %s had %d files\n' "$1" "$count"

Confirm the existence of one or more directories

I'd like to check of the existence of one or more directories in a Bash script using a wildcard.
I've tried this;
if [ -d app/*management ]
then
for mscript in `ls -d app/*management`
do
...
done
fi
Which works if there is one match but throws the error "binary operator expected".
Any suggestion on a good way to do this?
You can't use -d to check multiple directories at the same time without && (and) in your expressions. I would use this:
for dir in app/*management; do
if [[ -d $dir ]]; then
...
fi
done
You should use globs instead of parsing the output of ls. See the following link for more information: http://mywiki.wooledge.org/ParsingLs
After the glob expands to your list of management directories, the if statement looks like
if [ -d app/1management app/2management ]
Since -d takes only one argument, bash doesn't know what to do with the remaining directories. To bash, it looks like you forgot to include a binary operator.
You can do just do the following:
for mscript in app/*management; do
if [ ! -d $mscript ]; then
continue
fi
...
done
EDIT: As jordanm commented, the following probably isn't necessary, but I'll leave it here for reference, as nullglob is good to know about.
One caveat. If there is a possibility that app/*management won't expand to anything, you need to set the shell option nullglob before your loop, or else "app/*management" will be treated as a literal string, not a shell glob.
if ! shopt nullglob; then
setting_nullglob=1
shopt -qs nullglob
fi
for mscript in app/*management; do
...
done
if [ ${setting_nullglob:-0} = 1 ]; then
unset setting_nullglob
shopt -qu nullglob
fi
Why don't you do it like this:
for mscript in app/*management ; do
if [ -d "$myscript" ] ; then
...
fi
done

List directory and find files with certain strings in the name

I am fairly new to shell scripting, so go easy on me please as I know this is most likely something real basic. My question is this, I need to write a script that will look at a directory and tell me if it finds a match for a string that I specify in the filenames. Here would be an example.
I have a directory named tmp. Inside that directory are files named tmp-a, temp-a, temporary-a, etc. If the script looks at the directory and sees that there is a filename with the string of 'tmp', or 'temp' it should continue with the script, but if it does not see any filenames matching a string specified in the shell script it should quit. I am basically looking for a conditional 'if [ -f filename ]' statement that can apply 'or'.
I hope that made sense and as always, thanks in advance.
Tim
The pattern tmp* expands to the list of files whose name begins with tmp, or to the single-word list consisting of the literal pattern itself if there is no matching file.
set --
for pattern in 'tmp*' 'temp*'; do
set -- $pattern "$#"
if [ ! -e "$1" ]; then shift; fi
done
if [ $# -eq 0 ]; then
echo "No matching file"
else
for x in "$#"; do …; done
fi
In bash, you can request the expansion of a pattern that matches no file to be the empty list, which simplifies matters a lot.
shopt -s nullglob
set -- tmp* temp*
if [ $# -eq 0 ]; then …
The same thing goes for zsh, which allows this to be set per-pattern.
set -- tmp*(N) temp*(N)
if [ $# -eq 0 ]; then …
If you wish to search recursively inside directories, you can use the find command.
if [ -n "$(find -name 'tmp*' -o -name 'temp*' | head -c 1)" ]; then
# there are matching files
fi

Resources