Often when writing for the bash shell, one needs to test if a file (or Directory) exists (or doesn't exist) and take appropriate action. Most common amongst these test are...
-e - file exists, -f - file is a regular file (not a directory or device file), -s - file is not zero size, -d - file is a directory, -r - file has read permission, -w - file has write, or -x execute permission (for the user running the test)
This is easily confirmed as demonstrated on this user-writable directory....
#/bin/bash
if [ -f "/Library/Application Support" ]; then
echo 'YES SIR -f is fine'
else echo 'no -f for you'
fi
if [ -w "/Library/Application Support" ]; then
echo 'YES SIR -w is fine'
else echo 'no -w for you'
fi
if [ -d "/Library/Application Support" ]; then
echo 'YES SIR -d is fine'
else echo 'no -d for you'
fi
➝ no -f for you ✓
➝ YES SIR -w is fine ✓
➝ YES SIR -d is fine ✓
My question, although seemingly obvious, and unlikely to be impossible - is how to simply combine these tests, without having to perform them separately for each condition... Unfortunately...
if [ -wd "/Library/Application Support" ]
▶ -wd: unary operator expected
if [ -w | -d "/Library/Application Support" ]
▶ [: missing `]'
▶ -d: command not found
if [ -w [ -d "/Library.... ]] & if [ -w && -d "/Library.... ]
▶ [: missing `]'
➝ no -wd for you ✖
➝ no -w | -d for you ✖
➝ no [ -w [ -d .. ]] for you ✖
➝ no -w && -d for you ✖
What am I missing here?
You can use logical operators to multiple conditions, e.g. -a for AND:
MYFILE=/tmp/data.bin
if [ -f "$MYFILE" -a -r "$MYFILE" -a -w "$MYFILE" ]; then
#do stuff
fi
unset MYFILE
Of course, you need to use AND somehow as Kerrek(+1) and Ben(+1) pointed it out. You can do in in few different ways. Here is an ala-microbenchmark results for few methods:
Most portable and readable way:
$ time for i in $(seq 100000); do [ 1 = 1 ] && [ 2 = 2 ] && [ 3 = 3 ]; done
real 0m2.583s
still portable, less readable, faster:
$ time for i in $(seq 100000); do [ 1 = 1 -a 2 = 2 -a 3 = 3 ]; done
real 0m1.681s
bashism, but readable and faster
$ time for i in $(seq 100000); do [[ 1 = 1 ]] && [[ 2 = 2 ]] && [[ 3 = 3 ]]; done
real 0m1.285s
bashism, but quite readable, and fastest.
$ time for i in $(seq 100000); do [[ 1 = 1 && 2 = 2 && 3 = 3 ]]; done
real 0m0.934s
Note, that in bash, "[" is a builtin, so bash is using internal command not a symlink to /usr/bin/test exacutable. The "[[" is a bash keyword. So the slowest possible way will be:
time for i in $(seq 100000); do /usr/bin/\[ 1 = 1 ] && /usr/bin/\[ 2 = 2 ] && /usr/bin/\[ 3 = 3 ]; done
real 14m8.678s
You want -a as in -f foo -a -d foo (actually that test would be false, but you get the idea).
You were close with & you just needed && as in [ -f foo ] && [ -d foo ] although that runs multiple commands rather than one.
Here is a manual page for test which is the command that [ is a link to. Modern implementations of test have a lot more features (along with the shell-builtin version [[ which is documented in your shell's manpage).
check-file(){
while [[ ${#} -gt 0 ]]; do
case $1 in
fxrsw) [[ -f "$2" && -x "$2" && -r "$2" && -s "$2" && -w "$2" ]] || return 1 ;;
fxrs) [[ -f "$2" && -x "$2" && -r "$2" && -s "$2" ]] || return 1 ;;
fxr) [[ -f "$2" && -x "$2" && -r "$2" ]] || return 1 ;;
fr) [[ -f "$2" && -r "$2" ]] || return 1 ;;
fx) [[ -f "$2" && -x "$2" ]] || return 1 ;;
fe) [[ -f "$2" && -e "$2" ]] || return 1 ;;
hf) [[ -h "$2" && -f "$2" ]] || return 1 ;;
*) [[ -e "$1" ]] || return 1 ;;
esac
shift
done
}
check-file fxr "/path/file" && echo "is valid"
check-file hf "/path/folder/symlink" || { echo "Fatal error cant validate symlink"; exit 1; }
check-file fe "file.txt" || touch "file.txt" && ln -s "${HOME}/file.txt" "/docs/file.txt" && check-file hf "/docs/file.txt" || exit 1
if check-file fxrsw "${HOME}"; then
echo "Your home is your home from the looks of it."
else
echo "You infected your own home."
fi
Why not write a function to do it?
check_file () {
local FLAGS=$1
local PATH=$2
if [ -z "$PATH" ] ; then
if [ -z "$FLAGS" ] ; then
echo "check_file: must specify at least a path" >&2
exit 1
fi
PATH=$FLAGS
FLAGS=-e
fi
FLAGS=${FLAGS#-}
while [ -n "$FLAGS" ] ; do
local FLAG=`printf "%c" "$FLAGS"`
if [ ! -$FLAG $PATH ] ; then false; return; fi
FLAGS=${FLAGS#?}
done
true
}
Then just use it like:
for path in / /etc /etc/passwd /bin/bash
{
if check_file -dx $path ; then
echo "$path is a directory and executable"
else
echo "$path is not a directory or not executable"
fi
}
And you should get:
/ is a directory and executable
/etc is a directory and executable
/etc/passwd is not a directory or not executable
/bin/bash is not a directory or not executable
This seems to work (notice the double brackets):
#!/bin/bash
if [[ -fwd "/Library/Application Support" ]]
then
echo 'YES SIR -f -w -d are fine'
else
echo 'no -f or -w or -d for you'
fi
Related
I'm creating a bash script and somewhere inside I have this code:
if [ $# -eq 2 -a (! -r "$2" -o ! -f "$2") ]; then
echo "rvf: bestand \""$2"\" bestaat niet of is onleesbaar" 1>&2
exit 2
fi
When i try to run this inside the script I get this error:
Syntax Error (bash -n):
rvf: line 14: syntax error in conditional expression
rvf: line 14: syntax error near `-a'
rvf: line 14: `if [[ $# -eq 2 -a (! -r "$2" -o ! -f "$2") ]]; then'
How does '()' work inside Bash scripts?
[[ doens't support -a, and it is considered obsolete and non portable for [. The correct solution using [ would be
if [ "$#" -eq 2 ] && { [ ! -r "$2" ] || [ ! -f "$2" ]; }; then
Grouping is done with { ... } rather than ( ... ) to avoid creating an unnecessary subshell.
Using [[ is simplifies to
if [[ "$#" -eq 2 && ( ! -r "$2" || ! -f "$2" ) ]]; then
Parentheses can be used for grouping inside [[; as a compound command, it uses separate parsing and evaluation rules, compared to an ordinary command like [ (which is just an alias for test, not syntax of any kind).
In either case, De Morgan's laws lets you refactor this to something a little simpler:
if [ "$#" -eq 2 ] && ! { [ -r "$2" ] && [ -f "$2" ] }; then
if [[ "$#" -eq 2 && ! ( -r "$2" && -f "$2" ) ]]; then
There are multiple points of confusion here.
[ can (as an optional XSI extension to the standard) support ( as a separate word (meaning there needs to be spaces around it), but the POSIX sh specification marks it (like -a and -o) as "obsolescent" and advises against its use.
[[ does support (, but again, it needs to always be a separate word.
Don't do that at all, though. You're using only well-supported and portable functionality if you keep each test its own simple command and combine them only with the shell's boolean logic support.
That is:
if [ "$#" -eq 2 ] && { [ ! -r "$2" ] || [ ! -f "$2" ]; }; then
echo "rvf: bestand \"$2\" bestaat niet of is onleesbaar" >&2
exit 2
fi
Restructure your logic.
"Not A or Not B" is just a more complicated way to say "not (A and B)".
In bash, try
if [[ "$#" == 2 ]] && ! [[ -r "$2" && -f "$2" ]]; then
Better,
if [[ "$#" == 2 && -r "$2" && -f "$2" ]]
then : all good code
else : nope code
fi
Even better,
if [[ "$#" == 2 ]] # correct args
then if [[ -r "$2" ]] # is readable
then if [[ if -f "$2" ]] # is a file
then echo "all good"
: do all good stuff
else echo "'$2' not a file"
: do not a file stuff
fi
else echo "'$2' not readable"
: do not readable stuff
fi
else echo "Invalid number of args"
: do wrong args stuff
fi
Clear error logging is worth breaking the pieces out.
Even better, imho -
if [[ "$#" != 2 ]]
then : wrong args stuff
fi
if [[ ! -r "$2" ]]
then : unreadable stuff
fi
if [[ ! -f "$2" ]]
then : do not a file stuff
fi
: do all good stuff
I am trying to source an auto-completion script as a non-root user but receive a 'Bad Substitution' error message.
I am able to source it as root though.
On a other server I am able to source the script as non-root user.
I am guessing it is not a permission issue since I receive the same error trying to source a copy of the script with full permissions.
I have tried to echo all the environment variables used in the script, no issue there.
As the auto-completion script is packaged with the software I use I would rather not modify it.
Anyone would have a hint on what could be missing to the user so I can source the script from it ?
Thanks in advance for any idea!
Edit1:
ps output:
PID TTY TIME CMD
3261 pts/0 00:00:00 bash
73620 pts/0 00:00:00 ps
Error only as non-root user:
/opt/splunk/share/splunk/cli-command-completion.sh: 7: /opt/splunk/share/splunk/cli-command-completion.sh: Bad substitution
Script:
# Vainstein K 12aug2013
# # # Check a few prereqs.
feature='"splunk <verb> <object>" tab-completion'
[ `basename $SHELL` != 'bash' ] && echo "Sorry, $feature is only for bash" >&2 && return 11
[ ${BASH_VERSINFO[0]} -lt 4 ] && echo "Sorry, $feature only works with bash 4.0 or higher" >&2 && return 12
[ `type -t complete` != 'builtin' ] && echo "Sorry, $feature requires a bash that supports programmable command completion" >&2 && return 13
die () {
echo "(exit=$?) $#" >&2 && exit 42
}
ifSourced () { # do NOT exit(1) from this function!
local readonly tempfile=`pwd`/tmp--cli-completion--$$
rm -f $tempfile
$BASH ${BASH_ARGV[0]} --populateTempfile $tempfile
[ $? -eq 0 ] || return
[ -e $tempfile ] || return
. $tempfile
rm -f $tempfile
# # # Associate the completion function with the splunk binary.
local readonly completionFunction=fSplunkComplete
complete -r splunk 2>/dev/null
complete -F $completionFunction splunk
# You can view the completion function anytime via: $ type fSplunkComplete
}
ifInvoked () { # all error checking happens in this function
local readonly debug=false
local readonly tempfile=$1
$debug && echo "Told that tempfile=$tempfile"
# # # If anything goes wrong, at least we don't pollute cwd with our tempfile.
$debug || trap "rm -f $tempfile" SIGINT SIGQUIT SIGTERM SIGABRT SIGPIPE
touch $tempfile || die "Cannot touch tempfile=$tempfile"
# # # Decide where SPLUNK_HOME is.
if [ "$(dirname $(pwd))" == 'bin' ]; then
local readonly splunkHome=$(dirname $(dirname $(pwd)))
elif [ -n "$SPLUNK_HOME" ]; then
local readonly splunkHome=$SPLUNK_HOME
else
die 'Cannot figure out where SPLUNK_HOME is'
fi
$debug && echo "Decided SPLUNK_HOME=$splunkHome"
# # # Check that splunk (the binary) exists.
local readonly splunkBinary=$splunkHome/bin/splunk
[ -e $splunkBinary -a -x $splunkBinary ] || die "Cannot find expected binary=$splunkBinary"
# # # Find the file with object->{verb1,verb2,...} map.
local readonly splunkrcCmdsXml=$splunkHome/etc/system/static/splunkrc_cmds.xml
[ -e $splunkrcCmdsXml ] || die "Cannot find expected file $splunkrcCmdsXml"
$debug && echo "Shall read verb-obj info from: $splunkrcCmdsXml"
# # # Parse the map file, and generate our internal verb->{objA,objB,...} map.
local -A verb_to_objects
local line object verb objectsForThisVerb lineNumber=0
local inItem=false
local readonly regex_depr='\<depr\>'
local readonly regex_verb='\<verb\>'
local readonly regex_synonym='\<synonym\>'
while read line; do
lineNumber=$((lineNumber+1))
if $inItem; then
if [[ $line =~ '</item>' ]]; then
$debug && echo "Exited item tag at line=$lineNumber; this was obj=$object"
inItem=false
object=''
elif [[ $line =~ '<cmd name' && ! $line =~ $regex_depr && ! $line =~ $regex_synonym ]]; then
[ -z "$object" ] && die "BUG: No object within item tag. (At line $lineNumber of $splunkrcCmdsXml)"
verb=${line#*\"} # remove shortest match of .*" from the front
verb=${verb%%\"*} # remove longest match of ".* from the back
[ "$verb" == '_internal' ] && continue # Why the... eh, moving on.
objectsForThisVerb=${verb_to_objects[$verb]}
objectsForThisVerb="$objectsForThisVerb $object"
verb_to_objects[$verb]=$objectsForThisVerb
$debug && echo "Mapped object=$object to verb=$verb at line=$lineNumber; now objectsForThisVerb='$objectsForThisVerb'"
fi
else # ! inItem
if [[ $line =~ '<item obj' && ! $line =~ $regex_depr && ! $line =~ $regex_verb && ! $line =~ $regex_synonym ]]; then
inItem=true
object=${line#*\"} # remove shortest match of .*" from the front
object=${object%%\"*} # remove longest match of ".* from the back
$debug && echo "Entered item tag at line=$lineNumber, parsed object=$object"
[ "$object" == 'on' ] && inItem=false # Do not expose Amrit's puerile jest.
[ "$object" == 'help' ] && inItem=false # Although 'help' is a verb, splunkrc_cmds.xml constructs it as an object; ugh. We'll deal with the objects (topics) of 'splunk help' separately, below.
fi
fi
done < $splunkrcCmdsXml
$debug && echo "Processed $lineNumber lines. Map keys: ${!verb_to_objects[*]}, values: ${verb_to_objects[#]}"
# # # Oh wait, '<verb> deploy-server' aren't in splunkrc_cmds.xml; thanks, Jojy!!!!!
for verb in reload enable disable display; do
objectsForThisVerb=${verb_to_objects[$verb]}
objectsForThisVerb="$objectsForThisVerb deploy-server"
verb_to_objects[$verb]=$objectsForThisVerb
done
# # # Find the file with topics understood by 'splunk help <topic>' command, and extract list of topics.
local readonly literalsPy=$splunkHome/lib/python2.7/site-packages/splunk/clilib/literals.py
[ -e $literalsPy ] || die "Cannot find expected file $literalsPy"
$debug && echo "Shall read help topics list from: $literalsPy"
local readonly helpTopics=$(sed '/^addHelp/! d; s/^addHelp//; s/,.*$//; s/[^a-zA-Z_-]/ /g; s/^[ ]*//; s/[ ].*$//; /^$/ d' $literalsPy | sort | uniq)
$debug && echo "Parsed help topics list as: $helpTopics"
#######################################################
# # # Write the completion function to tempfile: BEGIN.
local readonly completionFunction=fSplunkComplete
echo -e 'function '$completionFunction' () {' >> $tempfile
echo -e '\tlocal wordCur=${COMP_WORDS[COMP_CWORD]}' >> $tempfile
echo -e '\tlocal wordPrev=${COMP_WORDS[COMP_CWORD-1]}' >> $tempfile
echo -e '\tcase $wordPrev in' >> $tempfile
# # # What can follow 'splunk' itself? Verbs used in main.c to key the 'cmd_handlers' array; and verbs from splunkrc_cmds.xml; and 'help'.
local readonly keys__cmd_handlers='ftr start startnoss stop restart restartss status rebuild train fsck clean-dispatch clean-srtemp validate verifyconfig anonymize find clean createssl juststopit migrate --version -version version httpport soapport spool ftw envvars _RAW_envvars _port_check cmd _rest_xml_dump search dispatch rtsearch livetail _normalizepath _internal logout btool pooling _web_bootstart offline clone-prep-clear-config diag'
local allVerbs="${!verb_to_objects[*]}"
echo -e '\t\tsplunk)\n\t\t\tCOMPREPLY=( $(compgen -W "'$keys__cmd_handlers $allVerbs' help" -- $wordCur) ) ;;' >> $tempfile
# # # What can follow 'splunk _internal'? see cmd_internal() of main.c
local readonly actions_internal='http mgmt https pre-flight-checks check-db call rpc rpc-auth soap-call soap-call-auth prefixcount totalcount check-xml-files first-time-run make-splunkweb-certs-and-var-run-merged'
echo -e '\t\t_internal)\n\t\t\tCOMPREPLY=( $(compgen -W "'$actions_internal'" -- $wordCur) ) ;;' >> $tempfile
# # # Options to 'splunk clean' are in CLI::clean() of src/main/Clean.cpp; to 'splunk fsck', in usageBanner of src/main/Fsck.cpp; to 'splunk migrate', in CLI::migrate() of src/main/Migration.cpp
echo -e '\t\tclean)\n\t\t\tCOMPREPLY=( $(compgen -W "all eventdata globaldata userdata inputdata locks deployment-artifacts raft" -- $wordCur) ) ;;' >> $tempfile
echo -e '\t\tfsck)\n\t\t\tCOMPREPLY=( $(compgen -W "scan repair clear-bloomfilter make-searchable" -- $wordCur) ) ;;' >> $tempfile
echo -e '\t\tmigrate)\n\t\t\tCOMPREPLY=( $(compgen -W "input-records to-modular-inputs rename-cluster-app" -- $wordCur) ) ;;' >> $tempfile
# # # List the help topics.
echo -e '\t\thelp)\n\t\t\tCOMPREPLY=( $(compgen -W "'$helpTopics'" -- $wordCur) ) ;;' >> $tempfile
# # # What can follow 'splunk cmd'? any executable in SPLUNK_HOME/bin/
echo -e '\t\tcmd)\n\t\t\tCOMPREPLY=( $(compgen -o default -o filenames -G "'$splunkHome'/bin/*" -- $wordCur) ) ;;' >> $tempfile
# # # Finally, let each verb be completed by its objects.
for verb in $allVerbs; do
echo -e '\t\t'$verb')\n\t\t\tCOMPREPLY=( $(compgen -W "'${verb_to_objects[$verb]}'" -- $wordCur) ) ;;' >> $tempfile
done
# # # And if we've run out of suggestions, revert to bash's default completion behavior: filename completion.
echo -e '\t\t*)\n\t\t\tCOMPREPLY=( $(compgen -f -- $wordCur) ) ;;' >> $tempfile
echo -e '\tesac' >> $tempfile
echo -e '}' >> $tempfile
$debug && cp $tempfile $tempfile~bak
# # # Write the completion function to tempfile: DONE.
######################################################
# # # Sanity check: source the tempfile, make sure that the function we wrote can be parsed and loaded by the shell.
unset $completionFunction
. $tempfile
[ "`type -t $completionFunction`" == 'function' ] || die 'BUG: generated completion function cannot be parsed by bash'
}
if [ $SHLVL -eq 1 ]; then
[ $# -ge 1 ] && echo "Ignoring supplied arguments: $#" >&2
ifSourced
elif [ $SHLVL -eq 2 ]; then
if [ $# -eq 2 ] && [ $1 == '--populateTempfile' ]; then
ifInvoked $2
else
echo -e "This script must be sourced, like so:\n\n\t\033[1m. $0\033[0m\n"
fi
else
: # user is running screen(1) or something of the sort.
fi
# # # Clean up.
unset die ifSourced ifInvoked
Edit2:
xtrace output
++ feature='"splunk <verb> <object>" tab-completion'
+++ basename /bin/bash
++ '[' bash '!=' bash ']'
++ '[' 4 -lt 4 ']'
+++ type -t complete
++ '[' builtin '!=' builtin ']'
++ '[' 1 -eq 1 ']'
++ '[' 0 -ge 1 ']'
++ ifSourced
+++ pwd
++ local readonly tempfile=/home/splunk/tmp--cli-completion--12431
++ rm -f /home/splunk/tmp--cli-completion--12431
++ /bin/sh cli-command-completion.sh --populateTempfile /home/splunk/tmp--cli-completion--12431
+ feature="splunk <verb> <object>" tab-completion
+ basename /bin/bash
+ [ bash != bash ]
cli-command-completion.sh: 8: cli-command-completion.sh: Bad substitution
++ '[' 2 -eq 0 ']'
++ return
++ unset die ifSourced ifInvoked
Edit3:
When using
set -o verbose
set -o noglob
set -o noglob
and checking differences between OK (root) and failing (non-root) run.
OK side:
[...]
+++ basename /bin/bash
++ '[' bash '!=' bash ']'
[ ${BASH_VERSINFO[0]} -lt 4 ] && echo "Sorry, $feature only works with bash 4.0 or higher" >&2 && return 12
++ '[' 4 -lt 4 ']'
[...]
++ local readonly tempfile=/home/splunk/tmp--cli-completion--42064
++ rm -f /home/splunk/tmp--cli-completion--42064
++ /bin/bash cli-command-completion.sh --populateTempfile /home/splunk/tmp--cli-completion--42064
+ set -o verbose
set -o noglob
+ set -o noglob
# Vainstein K 12aug2013
[...]
[ ${BASH_VERSINFO[0]} -lt 4 ] && echo "Sorry, $feature only works with bash 4.0 or higher" >&2 && return 12
+ '[' 4 -lt 4 ']'
[...]
Failing side:
[...]
+++ basename /bin/bash
++ '[' bash '!=' bash ']'
[ ${BASH_VERSINFO[0]} -lt 4 ] && echo "Sorry, $feature only works with bash 4.0 or higher" >&2 && return 12
++ '[' 4 -lt 4 ']'
[...]
++ local readonly tempfile=/home/splunk/tmp--cli-completion--40686
++ rm -f /home/splunk/tmp--cli-completion--40686
++ /bin/sh cli-command-completion.sh --populateTempfile /home/splunk/tmp--cli-completion--40686
+ set -o verbose
set -o noglob
+ set -o noglob
# Vainstein K 12aug2013
# # # Check a few prereqs.
feature='"splunk <verb> <object>" tab-completion'
+ feature="splunk <verb> <object>" tab-completion
[ `basename $SHELL` != 'bash' ] && echo "Sorry, $feature is only for bash" >&2 && return 11
+ basename /bin/bash
+ [ bash != bash ]
[ ${BASH_VERSINFO[0]} -lt 4 ] && echo "Sorry, $feature only works with bash 4.0 or higher" >&2 && return 12
cli-command-completion.sh: 10: cli-command-completion.sh: Bad substitution
++ '[' 2 -eq 0 ']'
++ return
# # # Clean up.
unset die ifSourced ifInvoked
++ unset die ifSourced ifInvoked
Seems like as non-root, it runs as /bin/sh whereas as 'bash' as root.
Weird because I tried multiple things to force bash there, maybe not the right ones.
It also fail at line 7 even though the first loop succeeds.
This should do the trick (line 7):
[ $(type -t complete) != 'builtin' ] && echo "Sorry, $feature requires a bash that supports programmable command completion" >&2 && return 13
Turns out from trace output that /bin/sh was used instead of /bin/bash to launch the script because /bin/bash was not set as default shell.
I have changed the default shell to /bin/bash and it is all ok now :)
I have used this method to set /bin/bash as default shell :
Check:
grep /etc/passwd
Change:
chsh --shell /bin/bash
Check modification:
grep /etc/passwd
Thanks for the help!
I have a bash script which is intended to be idempotent. It creates symlinks, and it should be okay if the links are already there.
Here's an extract
L="/var/me/foo"
if [[ -e "$L" ]] && ! [[ -L "$L" ]];
then
echo "$L exists but is not a link."
exit 1;
elif [[ -e "$L" ]] && [[ -L "$L" ]];
then
echo "$L exists and is a link."
else
ln -s "/other/place" "$L" ||
{
echo "Could not chown ln -s for $L";
exit 1;
}
fi
The file /var/me/foo is already a symlink pointing to /other/place, according to ls -l.
Nevertheless, when I run this script the if and elif branches are not entered, instead we go into the else and attempt the ln, which fails because the file already exists.
Why do my tests not work?
Because you only check [ -L "$L" ] if [ -e "$L" ] is true, and [ -e "$L" ] returns false for a link pointing to a destination that doesn't exist, you don't detect links that point to locations that don't exist.
The below logic is a bit more comprehensive.
link=/var/me/foo
dest=/other/place
# because [[ ]] is in use, quotes are not mandatory
if [[ -L $link ]]; then
echo "$link exists as a link, though its target may or may not exist" >&2
elif [[ -e $link ]]; then
echo "$link exists but is not a link" >&2
exit 1
else
ln -s "$dest" "$link" || { echo "yadda yadda" >&2; exit 1; }
fi
if [ "$#" -ne 1 ]; then
flag=1;
elif ! [[ "$1" == "arg1" || "$1" == "arg2" || "$1" == "arg3" || ...... ]]; then
echo "Invalid"
flag=1;
fi
if [ "$flag" == "1" ]; then
echo "Usage of script...."
exit
fi
count="$(ls *.mov | wc -l)"
if [[ "$count" -eq 0 ]]
then
echo there are 0 .mov files in this path
elif [[ "$count" -eq 1 ]]
then
echo there is 1 .mov file in this path
vlc *.mov
elif [[ $1 = "arg1" ]] ; then
echo entered the tough part....coz its not entering`enter code here`
elif [[ "$1" == "arg2" ]] || [[ "$1" == "arg10" ]] ; then
echo entered here atleast...but not entering
else
script continues
The code does not enter elif conditions involving command line arguments. Tried =, ==, -eq, double square braces, single square braces. But it does not enter, pls help
you should add a disclaimer at the head of the script: " #!/bin/bash"
I tried your script and it does get into the elif.
the code I used is:
if [ "$#" -ne 1 ]; then
echo "not 1 arg"
flag=1;
elif ! [[ "$1" == "arg1" || "$1" == "arg2" || "$1" == "arg3" ]]; then
echo "Invalid"
flag=1;
else
echo "else"
fi
and the input/output are:
$ . script.sh 1 2
not 1 arg
$ . script.sh 1
Invalid
$ . script.sh arg1
else
I tried the second part and it is also working:
count=$2
if [[ "$count" -eq 0 ]] ;then
echo "there are 0 .mov files in this path"
elif [[ "$count" -eq 1 ]] ;then
echo "there is 1 .mov file in this path"
vlc *.mov
elif [[ $1 = "arg1" ]] ; then
echo "arg1 "
elif [[ "$1" == "arg2" ]] || [[ "$1" == "arg10" ]] ; then
echo "arg2 or arg10 "
else
echo "else"
fi
and tested it (second argument is "count"):
$ . script.sh arg1 0
there are 0 .mov files in this path
$ . script.sh 1 0
there are 0 .mov files in this path
$ . script.sh arg10 0
there are 0 .mov files in this path
$ . script.sh arg1 1
there is 1 .mov file in this path
The program 'vlc' is currently not installed. To run 'vlc' please ask your administrator to install the package 'vlc-nox'
$ . script.sh arg10 1
there is 1 .mov file in this path
The program 'vlc' is currently not installed. To run 'vlc' please ask your administrator to install the package 'vlc-nox'
$ . script.sh 1 1
there is 1 .mov file in this path
The program 'vlc' is currently not installed. To run 'vlc' please ask your administrator to install the package 'vlc-nox'
$ . script.sh arg1 2
arg1
$ . script.sh 1 2
else
$ . script.sh arg10 2
arg2 or arg10
You should look into the bash case statement http://mywiki.wooledge.org/BashGuide/TestsAndConditionals#Choices_.28case_and_select.29
For this particular script, it will make your script easier to read and your branching problem will most likely go away..
if your purpose is to handle the arguments passed to the script by command line it's better to use the getopts bash
this is just an example that you can adapt to your scope:
#!/bin/bash
function usage {
echo "usage: ..."
}
while getopts f:o:h opt; do
case $opt in
f)
fileName=$OPTARG
echo "filename[$fileName]"
;;
o)
otherargs=$OPTARG
echo "otherargs[$otherargs]"
;;
h)
usage && exit 0
;;
?)
usage && exit 2
;;
esac
done
~
output
[myShell] ➤ ./n -h
usage: ...
[myShell] ➤ ./n -f myfilename
filename[myfilename]
[myShell] ➤ ./n -o other
otherargs[other]
[myShell] ➤ ./n -l
./n: illegal option -- l
usage: ...
for var in "$#"
do
if test -z $var
then
echo "missing operand"
elif [ -d $var ]
then
echo "This is a directory"
elif [ ! -f $var ]
then
echo "The file does not exist"
else
basename=$(basename $var)
dirname=$(readlink -f $var)
inodeno=$(ls -i $var| cut -d" " -f1)
read -p "remove regular file $#" input
if [ $input = "n" ]
then exit 1
fi
mv $var "$var"_"$inodeno"
echo "$basename"_"$inodeno":"$dirname" >> $HOME/.restore.info
mv "$var"_"$inodeno" $HOME/deleted
fi
done
**Hello, the above code is trying to mimic the rm command in unix. Its purpose is to remove the file .
Eg if I type in bash safe_rm file1 , it works however if type in
bash safe_rm file1 file 2 , it prompts me to remove file 1 twice and gives me a unary operater expected for line 27(if [ $input = "n" ]).
Why does it not work for two files, ideally I would like it to prompt me to remove file1 and file 2.
Thanks
read -p "remove regular file $#" input
should probably be
read -p "remove regular file $var" input
That's the basic.
And this is how I'd prefer to do it:
for T in "$#"; do
if [[ -z $T ]]; then
echo "Target is null."
elif [[ ! -e $T ]]; then
echo "Target does not exist: $T"
elif [[ -d $T ]]; then
echo "Target can't be a directory: $T"
else
BASE=${T##*/}
DIRNAME=$(exec dirname "$T") ## Could be simpler but not sure how you want to use it.
INODE_NUM=$(exec stat -c '%i' "$T")
read -p "Remove regular file $T? "
if [[ $REPLY == [yY] ]]; then
# Just copied. Not sure about its logic.
mv "$T" "${T}_${INODE_NUM}"
echo "${BASE}_${INODE_NUM}:${DIRNAME}" >> "$HOME/.restore.info"
mv "${T}_${INODE_NUM}" "$HOME/deleted"
fi
fi
done