I am trying to run a basic bash program with two outputs, the program works and the tests passes. The thing is I want to reduce the output with only one "echo", when there's an input, show One for "input" and when is empty, One for you. This works but I want the out put in one line.
if (($# == 0))
then
echo "One for you, one for me."
else
echo "One for $1, one for me."
fi
echo "One for ${1:-you}, one for me."
When $1 is null or unset, then place you there.
For reference see bash manual shell parameter expansion.
Alternatively, if you have zero arguments, you could set "default" ones.
if (($# == 0)); then
set -- you
fi
echo "One for $1, one for me."
which is equivalent, because it will handle empty argument $1="" differently.
If you want shorter code, this may help:
other="you"; [[ $# -ne 0 ]] && other="$1"
echo "One for ${other}, one for me."
The first line defaults the other variable to be you and then overwrites it if you have provided a name. The second line just prints it out for you.
That won't help if the user supplies an empty name (as with script.sh "") so you may instead want to just check the variable itself rather than the count:
other="you"; [[ -n "$1" ]] && other="$1"
echo "One for ${other}, one for me."
Related
if is followed by then in bash but I don't understand why then cannot be used in the same line like if [...] then it has to be used in the next line. Does that remove some ambiguity from the code? or bash is designed like that? what is the underlying reason for it?
I tried to write if and then in the same line but it gave the error below:
./test: line 6: syntax error near unexpected token \`fi'
./test: line 6: \`fi'
the code is:
#!/bin/bash
if [ $1 -gt 0 ] then
echo "$1 is positive"
fi
It has to be preceded by a separator of some description, not necessarily on the next line(a). In other words, to achieve what you want, you can simply use:
if [[ $1 -gt 0 ]] ; then
echo "$1 is positive"
fi
As an aside, for one-liners like that, I tend to prefer:
[[ $1 -gt 0 ]] && echo "$1 is positive"
But that's simply because I prefer to see as much code on screen as possible. It's really just a style thing which you can freely ignore.
(a) The reason for this can be found in the Bash manpage (my emphasis):
RESERVED WORDS: Reserved words are words that have a special meaning to the shell. The following words are recognized as reserved when unquoted and either the first word of a simple command (see SHELL GRAMMAR below) or the third word of a case or for command:
! case coproc do done elif else esac fi for function if in select then until while { } time [[ ]]
Note that, though that section states it's the "first word of a simple command", the manpage seems to contradict itself in the referenced SHELL GRAMMAR section:
A simple command is a sequence of optional variable assignments followed by blank-separated words and redirections, and terminated by a control operator. The first word specifies the command to be executed, and is passed as argument zero.
So, whether you consider it part of the next command or a separator of some sort is arguable. What is not arguable is that it needs a separator of some sort (newline or semicolon, for example) before the then keyword.
The manpage doesn't go into why it was designed that way but it's probably to make the parsing of commands a little simpler.
Here's another way to explain the need for a line break or semicolon before then: the thing that goes between if and then is a command (or sequence of commands); if the then just came directly after the command without a delimiter, it'd be ambiguous whether it should be treated as a shell keyword or just an argument to the command.
For instance, this is a perfectly valid command:
echo This prints a phrase ending with then
...which prints "This prints a phrase ending with then". Now, consider this one:
if echo This prints a phrase ending with then
should that print "This prints a phrase ending with then" and look for a then keyword later on, or should it just print "This prints a phrase ending with" and treat the then as a keyword?
In order to settle this ambiguity, shell syntax says it should treat "then" as an argument to echo, and in order to get it treated as a keyword you need a command delimiter (line break or semicolon) to mark the end of the command.
Now, you might think that your if condition [ $1 -gt 0 ], already has a perfectly good delimiter, namely the ]. But in shell syntax, that's really just an argument to the [ command (yes, that's a command). Try this command:
[ 1 -gt 0 ] then
...and you'll probably get an error like "-bash: [: missing ']'", because the [ command checked its last argument to make sure it was "]", found that it was "then" instead, and panicked.
Perhaps it helps to understand why this is so by way of a few examples. The argument to if is a sequence of commands; so you can say e.g.
if read -r -p "What is your name?" name
[ "$name" -eq "tripleee" ]
then
echo "I kneel before thee"
fi
or even a complex compound like
while read -r -p "Favorite number?" number
case $number in
42) true; break;;
*) false;;
esac
do
echo "Review your preferences, then try again"
done
This extremely powerful but potentially confusing feature of the shell is probably one of its most misunderstood constructs. The ability to pass a sequence of commands to the flow control statements can make for very elegant scripts, but is often missed entirely (see e.g. Why is testing "$?" to see if a command succeeded or not, an anti-pattern?)
If it helps, you can use semi-colons
if [ $1 -gt 0 ]; then
echo "$1 is positive"
fi
# or even
if [ $1 -gt 0 ]; then echo "$1 is positive"; fi
As for why, it helps me to think of if, then, else, and fi as bash commands, and just like all other commands, they need to be at the start of a line (or after a semi-colon).
Hi I have written small shell script, I am not able to understand the behavior of that script. can any one help me to understand that script.
Script:
#!/bin/bash
if [ -z $1 ]
then
echo "fail"
else
echo "success"
fi
While executing the script .
./test.sh one
It exuting the else statement instead of main statement , even though its passing the argument.
can any one explain me this behavior to understand
The -z test in bash is checking if a string is an empty (zero length) value.
Since you're passing an argument to the script $1 is not empty and therefore -z $1 evaluates to false, executing the else portion of your script.
Side note: Since you're working with strings I recommend you to quote variables as follows:
if [ -z "$1" ]; then
echo "String is empty / No argument given"
else
echo "String is not empty / Argument given"
fi
Edit:
As pointed out by user1934428 it's probably better to use [[ instead of [. This, among others, eliminates the need for quoting. See more differences here.
if [[ -z $1 ]]; then
...
However, be aware that this is a bash extension and won't work in sh scripts.
I have a bash script which is getting options (using getopts). Let say they are '-n' , '-d' , '-u' . I only want to have one of the option being chosen, if not, it will prompt the user error.
The code is like this:
while getopts ":dun" name; do
case $name in
d )
DELETE='YES'
;;
u )
UPDATE='YES'
;;
n )
NEW='YES'
;;
esac
done
I can only have $DELETE or $UPDATE or $NEW being 'YES' in one time, meaning the user cannot specific '-n' and '-d' in the same time or '-u' and '-n' in the same time, how do I achieve that in a IF statement ?
I have been looking for this in stackoverflow, but can't find any. Thanks for the help, mate!
You can increment a counter every time getopts() senses one of the valid commandline options. Then, after the loop test the counter's value. If it is greater than one, then multiple options were specified.
This is a complete hack, and depends on the option variables being either unset or "YES" (and in the form I've written it, is bash-only):
if [[ "$DELETE$UPDATE$NEW" == YESYES* ]]; then
echo "Please only use one of -d, -u, and -n" >&2
exit 1
fi
(If you were using the brand-x shell instead of bash, it'd be something like if [ "$DELETE$UPDATE$NEW" = "YESYES" -o "$DELETE$UPDATE$NEW" = "YESYESYES" ]; then)
You're better off setting your variables to true or false, so you can simply write
if $DELETE; then
echo "You've already specified UPDATE!"
fi
and similar.
However, options are called that because they are supposed to be optional, and usually orthogonal. (There are a few instances where options of common utilities exclude each other, but the vast majority don't). What you want is really a mandatory mode of operation, so you shouldn't receive it as an option at all, but simply as the first command-line argument.
You can use some getopt-kludge to manage having only one of the option being chosen.
#!/bin/bash
while [ $# -gt 0 ]; do
case "$1" in
-u)
echo "got -u"
break
;;
-d)
echo "got -d"
break
;;
-n)
echo "got -n"
break
;;
*)
echo "some error"
break
;;
esac
done
I am trying to learn shell scripting and I am kind of confused with the idea of := or default value
#!/bin/sh
echo "Please enter a number \c"
read input
input=$((input % 2))
if [ $input -eq 0 ]
then
echo "The number is even"
else
echo "The number is odd"
fi
echo "Beginning of second part"
a="BLA"
a="Dennis"
echo $a
unset a
echo "a after unsetting"
echo $a
${a:=HI}
echo "unsetting a again"
unset a
echo $a
And I get this
Dennis
a after unsetting
./ifstatement.sh: line 21: HI: command not found
unsetting a again
When you write
${a:=HI}
the shell splits the result of the expansion into words, and interprets the first word as a command, as it would for any command line.
Instead, write
: "${a:=HI}"
: is a no-op command. The quotes prevent the shell from trying to do globbing, which in rare circumstances could cause a slowdown or an error.
There isn't a way to set a value that a variable will always "fall back" to when you un-set it. When you use the unset command, you are removing the variable (not just clearing the value associated with it) so it can't have any value, default or otherwise.
Instead, try a combination of two things. First, make sure the variable gets initialized. Second, create a function that sets the variable to the desired default value. Call this variable instead of unset. With this combination, you can simulate a variable having a "default" value.
${a:=HI} expands to HI, which your shell then tries to run as a command. If you're just trying to set the value of a variable if it is not set, you may want to do something like [ -z "$b" ] && b=BYE
Instead of calling unset $a, you do ${a:=HI} again
I have to modify an existing ksh script which looks at the command-line arguments using 'shift', and so empties $#, but now want to pass the original arguments to a second script afterwards.
In the mainline case I can do this by coping $# to a variable and passing that to the second script, but I can't get it to work for quoted command-line arguments.
If I have a script called 'printer' like below:
#!/bin/ksh
INPUT=$#
echo "Printing args"
until [[ $# -eq 0 ]];do
echo $1
shift
done
./printer2 $INPUT
and printer2 like below:
#!/bin/ksh
echo "Printing second args"
until [[ $# -eq 0 ]];do
echo $1
shift
done
I would like the output of
./printer first second "third forth"
to be :
Printing args
first
second
third forth
Printing second args
first
second
third forth
I've tried various combinations of quotes around variables (both in the assignment of $INPUT and when passing it to printer2) but can't figure it out. Can anyone help?
Ok I think I've found the solution after an awful lot of trial and error.
Assigning $INPUT like this:
set -A INPUT "$#"
and then passing it like this:
./printer2 "${INPUT[#]}"
produces the output I'm after.
The whole first script is therefore:
#!/bin/ksh
set -A INPUT "$#"
echo "Printing args"
until [[ $# -eq 0 ]];do
echo $1
shift
done
./printer2 "${INPUT[#]}"
and
./printer first second "third fourth"
outputs:
Printing args
first
second
third fourth
Printing second args
first
second
third fourth
If anyone wants to explain the problem with the other things I tried, please do, as I'm still interested!