Is there an inline-if with assignment (ternary conditional) in bash? [duplicate] - bash

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Ternary operator (?:) in Bash
If this were AS3 or Java, I would do the following:
fileName = dirName + "/" + (useDefault ? defaultName : customName) + ".txt";
But in shell, that seems needlessly complicated, requiring several lines of code, as well as quite a bit of repeated code.
if [ $useDefault ]; then
fileName="$dirName/$defaultName.txt"
else
fileName="$dirName/$customName.txt"
fi
You could compress that all into one line, but that sacrifices clarity immensely.
Is there any better way of writing an inline if with variable assignment in shell?

Just write:
fileName=${customName:-$defaultName}.txt
It's not quite the same as what you have, since it does not check useDefault. Instead, it just checks if customName is set. Instead of setting useDefault when you want to use the default, you simply unset customName.

There is no ?: conditional operator in the shell, but you could make the code a little less redundant like this:
if [ $useDefault ]; then
tmpname="$defaultName"
else
tmpname="$customName"
fi
fileName="$dirName/$tmpname.txt"
Or you could write your own shell function that acts like the ?: operator:
cond() {
if [ "$1" ] ; then
echo "$2"
else
echo "$3"
fi
}
fileName="$dirname/$(cond "$useDefault" "$defaultName" "$customName").txt"
though that's probably overkill (and it evaluates all three arguments).
Thanks to Gordon Davisson for pointing out in comments that quotes nest within $(...).

Related

Difference between 'if [ "x$foo" = "x1" ]' vs 'if [ "$foo" = "1" ]' in bash? [duplicate]

This question already has answers here:
Why do shell script comparisons often use x$VAR = xyes?
(7 answers)
Closed 3 years ago.
I'm reading Gitlab's source code to learn more about how it works. In the run bash script located inside the project root directory, I see the following:
if [ "x$GDK_RUNIT" = "x1" ]; then
...
fi
I know this conditional's purpose is to check if the value of the GDK_RUNIT envar is set equal to 1, and to exit with a non-zero return code if that's the case. My question is, what is the difference between the above code and the following:
if ["$GDK_RUNIT" = "1" ]; then
...
fi
In other words, what is the purpose of placing "x" before both the envar name and "1"?
I checked man test for anything related to x (since I know if and test are functionally equivalent), but all I saw was the -x flag.
x$GDK_RUNIT doesn't look like a flag to me, so I assumed the contents of the man page wasn't relevant.
It's completely unnecessary. There are old implementations of test which cannot handle an empty argument, so if GDK_RUNIT is undefined or empty, it avoids the equivalent of [ "" = "1" ], replacing it with [ "x" = "x1" ] instead. Just about any character or string would work in place of x, but the use of x was conventional.
However, no reasonably modern implementation, and certainly not bash's, has this problem.

How can I write if/else with Boolean in Bash? [duplicate]

This question already has answers here:
How can I declare and use Boolean variables in a shell script?
(25 answers)
Closed 5 years ago.
How can I write an 'if then' statement to switch between these to variables as in the following?
if(switch){
server_var_shortname=$server_shared_shortname
server_var=$server_shared
server_var_bare=$server_shared_bare
} else {
server_var_shortname=$server_vps_shortname
server_var=$server_vps
server_var_bare=$server_vps_bare
}
I'm not familiar with Bash syntax and basically just need an 'if/else' statement on a Boolean. Also, can I use true / false values as such? Also how do I do the 'else' statement?
$switch=true;
if $switch
then
server_var_shortname=$server_shared_shortname
server_var=$server_shared
server_var_bare=$server_shared_bare
fi
First, shells (including Bash) don't have Booleans; they don't even have integers (although they can sort of fake it). Mostly, they have strings.
Bash also has arrays... of strings. There are a number of ways of faking Booleans; my favorite is to use the strings "true" and "false". These also happen to be the names of commands that always succeed and fail respectively, which comes in handy, because the if statement actually takes a command, and runs the then clause if it succeeds and the else clause if it fails. Thus, you can "run" the Boolean, and it'll succeed if set to "true" and fail if set to "false". Like this:
switch=true # This doesn't have quotes around it, but it's a string anyway.
# ...
if $switch; then
server_var_shortname=$server_shared_shortname
server_var=$server_shared
server_var_bare=$server_shared_bare
else
server_var_shortname=$server_vps_shortname
server_var=$server_vps
server_var_bare=$server_vps_bare
fi
Note that the more usual format you'll see for if has square-brackets, like if [ something ]; then. In this case, [ is actually a command (not some funny sort of grouping operator) that evaluates its argument as an expression; thus [ "some string" = "some other string" ] is a command that will fail because the strings aren't equal. You could use if [ "$switch" = true ]; then, but I prefer to cheat and use the fake Boolean directly.
Caveat: if you do use the cheat I'm suggesting, make sure your "Boolean" variable is set to either "true" or "false" -- not unset, not set to something else. If it's set to anything else, I take no responsibility for the results.
Some other syntax notes:
Use $ on variables when fetching their values, not when assigning to them. You have $switch=true; up there, which will get you an error.
Also, you have a semicolon at the end of that line. This is unnecessary; semicolons are used to separate multiple commands on the same line (and a few other places), but they aren't needed to end the last (/only) command on a line.
The [ command (which is also known as test) has a kind of weird syntax. Mostly because it's a command, so it goes through the usual command parsing, so e.g. [ 5 > 19 ] is parsed as [ 5 ] with output sent to a file named "19" (and is then true, because "5" is nonblank). [ 5 ">" 19 ] is better, but still evaluates to true because > does string (alphabetical) comparisons, and "5" is alphabetically after "19". [ 5 -gt 19 ] does the expected thing.
There's also [[ ]] (similar, but cleaner syntax and not available in all shells) and (( )) (for math, not strings; also not in all shells). See Bash FAQ #31.
Putting commands in variables is generally a bad idea. See Bash FAQ #50.
shellcheck.net is your friend.
Bash doesn't have any concept of Boolean - there are no true / false values. The construct
[ $switch ]
will be true except when switch variable is not set or is set to an empty string.
[ ] && echo yes # Nothing is echoed
[ "" ] && echo yes # Nothing is echoed
unset switch && [ $switch ] && echo yes # Nothing is echoed
switch=1 && [ $switch ] && echo yes # 'yes' is echoed
switch=0 && [ $switch ] && echo yes # 'yes' is echoed - the shell makes no distinction of contents - it is true as long it is not empty
See also:
How can I declare and use Boolean variables in a shell script?
Here is a good guide for If else. But I want to show a different approach (which you will find also in the link on page 3).
Your coding looks like JavaScript, so I think with Switch you could also mean the case command instead of if. Switch in JavaScript is similar to case within a shell, but there isn't any method to check for Booleans. You can check string values for like true and false, and you can check for numbers.
Example...
#!/bin/bash
case "$Variable" in
false|0|"")
echo "Boolean is set to false."
;;
*)
echo "Boolean is set to true."
;;
esac
Addition
Keep in mind, there are many programs and tools that uses Boolean values in different forms.
Two examples...
SQL in general uses numbers as Boolean.
JavaScript uses true and false values.
Meaning: Your Bash script has to know the format of Booleans, before processing them!
You need something like this:
if
CONDITION_SEE_BELOW
then
server_var_shortname=$server_shared_shortname
server_var=$server_shared
server_var_bare=$server_shared_bare
else
server_var_shortname=$server_vps_shortname
server_var=$server_vps
server_var_bare=$server_vps_bare
fi
In Bash (and other shells), the CONDITION_SEE_BELOW part has to be a command. A command returns a numerical value, and by convention 0 means "true" and any non-zero value means "false". The then clause will execute if the command returns 0, or the else clause in all other cases. The return value is not the text output by the command. In shells, you can access it with the special variable expansion $? right after executing a command.
You can test that with commands true and false, which do one thing: generate a zero (true) and non-zero (false) return value. Try this at the command line:
true ; echo "true returns $?"
false ; echo "false returns $?"
You can use any command you want in a condition. There are two commands in particular that have been created with the idea of defining conditions: the classic test command [ ] (the actual command only being the opening bracket, which is also available as the test command), and the double-bracketed, Bash-specific [[ ]] (which is not technically a command, but rather special shell syntax).
For instance, say your switch variable contains either nothing (null string), or something (string with at least one character), and assume in your case you mean a null string to be "false" and any other string to be "true". Then you could use as a condition:
[ "$switch" ]
If you are OK with a string containing only spaces and tabs to be considered empty (which will happen as a result of standard shell expansion and word splitting of arguments to a command), then you may remove the double quotes.
The double-bracket test command is mostly similar, but has some nice things about it, including double-quoting not being needed most of the time, supporting Boolean logic with && and || inside the expression, and having regular expression matching as a feature. It is, however a Bashism.
You can use this as a reference to various tests that can be performed with both test commands:
6.4 Bash Conditional Expressions
If at all interested in shell programming, be sure to find out about the various tests you can use, as you are likely to be using many of them frequently.
As addition to Gordon's excellent answer, in Bash you can also use the double-parentheses construct. It works for integers, and it is the closest form to other languages. Demo:
for i in {-2..2}; do
printf "for %s " "$i"
if (( i )) # You can omit the `$`
then
echo is nonzero
else
echo is zero
fi
done
Output:
for -2 is nonzero
for -1 is nonzero
for 0 is zero
for 1 is nonzero
for 2 is nonzero
You can use any arithmetic operations inside, e.g.:
for i in {1..6}; do
printf "for %s " "$i"
if (( i % 2 )) #modulo
then
echo odd
else
echo even
fi
done
Output
for 1 odd
for 2 even
for 3 odd
for 4 even
for 5 odd
for 6 even

In shell, How do I efficiently process arguments passed to a function to invoke other programs without using too many conditional statements?

I don't have much practical programming experience.
I wrote a function in shell script to take parameters based on whose values an external program is invoked with other parameters. (the program name is passed as a parameter to the function, $1 in this case).
Somehow I find this code shitty and would like to reduce the lines and make it more efficient. I feel like there are too many conditions here and there might be an easier way to do this. Any ideas? There has to be a better way to do this. Thanks.
Code is here
You can build the argument list as an array, with separate conditionals for each variable part of the list (rather than nested conditionals as you have). This is very similar to Roland Illig's answer, except that using an array rather than a text variable avoids possible problems with funny characters (like spaces) in arguments (see BashFAQ #050) -- it's probably not needed here, but I prefer to do it the safe way to avoid surprises later. Also, note that I've assumed the arguments must be in a certain order -- if -disp can go before `refresh, then the "all versions" parts can all be put together at the beginning.
# Build the argument list, depending on a variety of conditions
arg_list=("-res" "$2" "$3") # all versions start this way
if [ "$7" = "allresolutions" ]; then
# allresolutions include the refresh rate parameter to be passed
arg_list+=("-refresh" "$4")
fi
arg_list+=("-disp" "$5") # all versions need the disp value
if [ "$6" = "nooptions" ]; then
: # nothing to add
elif [ "$6" = "vcaa" ]; then
arg_list+=("-vcaa" "5")
elif [ "$6" = "fsaa" ]; then
arg_list+=("-fsaa" "4")
elif [ "$6" = "vcfsaa" ]; then
arg_list+=("-vcaa" "5" "-fsaa" "4")
fi
if [ "$1" != "gears" ]; then
# ctree has time-to-run passed as parameter to account for suitable time for
# logging fps score. If bubble fps capture strategy is different from ctree,
# then this if statement has to be modified accordingly
arg_list+=("-sec" "$8")
fi
# Now actually run it
./"$1" "${arg_list[#]}" &>> ~/fps.log
Instead of the repeated inner "if … elif … fi" you can do this part of the argument processing once and save it in a variable (here: options).
case $6 in
nooptions) options="";;
vcaa) options="-vcaa 5";;
fsaa) options="-fsaa 4";;
vcfsaa) options="-vcaa 5 -fsaa 4";;
esac
...
./$1 -res $2 $3 -refresh $4 -disp $5 $options -sec $8
You could use getopts (see example) if you process that large number of arguments.

Having problems calling function within bash script

I've been working on our intro scripting assignment, and am having issues calling functions within the script. I am in the second portion of the assignment, and I am just testing to make sure what I have is (hopefully) going to work. I have gathered some directories, and ask a yes or no question. When I get a 'y', I wrote a little function that I call, and when I get a 'n' I have another function, both simple echoes. What is the issue?
part_two(){
answer=""
for value in "$#";do
echo "$value"
while [ "$answer" != "y" -a "$answer" != "n" ]
do
echo -n "Would you like to save the results to a file? (y/n): "
read answer
done
if [ "$answer" = "n" ]
then
part_six
elif [ "$answer" = "y" ]
then
part_five
fi
done
}
part_two $#
part_five(){
echo -n "working yes";
}
part_six(){
echo -n "working no";
}
Any help would be greatly appreciated, as always.
Much like in C a function must be defined before it's used. In your code snippet you are calling part_two (which is calling part_five and part_six) before declaring the two functions.
Have you tried moving their definitions to the start of the script?
EDIT:
In most cases, the best way to deal with this in Bash is to simply define all functions at the start of the script before executing any actual commands. The order of the definitions does not really matter - the shell only looks up a function when it's about to use it - so generally there are no dependency issues etc. that you may have to think about.
EDIT 2:
There are cases where you may not be able to just define a function at the start of the script. A common case is when you use conditional constructs to dynamically select or modify the declaration of a function e..g.:
if [[ "$1" = 0 ]]; then
function show() {
echo Zero
}
else
function show() {
echo Not-zero
}
fi
In these cases you have to make sure that each function call happens after that function (and any others that it calls) is declared.
EDIT 3:
In bash a function declaration is actually the function foo() { ... } block where you define its implementation - and yes, the function keyword is not strictly necessary. There are no function prototypes as in C - they would not make sense anyway because shell scripts are generally parsed as they are executed. Newer Bash version do read a script at once, but they mostly check for syntax errors and not for logical errors such as this one.
BTW the official term is "function declaration", but even the Bash info page uses "declaration" and "definition" interchangeably.

BASH: Assign '&' to variable NOT as string

I wanted to conditionally run a command as a background or foreground process, so I wrote something like this:
test $some_var; bg_suffix=&
long_command $bg_suffix
it doesn't work because bg_suffix is always empty whether it's been assigned or not.
But
test $some_var; bg_suffix="&"
long_command $bg_suffix
doesn't work either because now bg_suffix is interpreted as a string.
Any ideas how to solve this problem? Thanks!
Here is how to do it without using a quote-breaking eval
inBackground () {
t=$1
shift
if $t; then
"$#"&
else
"$#"
fi
}
This lets you do something like:
inBackground false echo '$$'
inBackground true sleep 4
This gets around the problem that all the eval-based solutions have: new and sometimes impossible quoting rules. For example, try to pass the '$$' through eval. Because true and false are not significant to the parser they can be in variables and things will still work.
Of course, if you wanted shell metachars to work (say, you redirect i/o) then eval is better, or you need to define a procedure for the command, and if you define a procedure, you problem is solved:
complicated_command () {
sleep 3
echo replace this with something complex
}
do_background=true
$do_background && (complicated_command&) || complicated_command
How about:
if [[ ${somevar} ]] ; then
long_command &
else
long_command
fi
or, if it is a long command you don't want to have to enter twice:
long_command=insert your big honking command here
if [[ ${somevar} ]] ; then
${long_command} &
else
${long_command}
fi
Just as an aside, I hope you're aware that the command sequence:
test ${condition}; x=2
will set x to 2 regardless of the test results. You may have meant to write:
test ${condition} && x=2
did you try
eval (long_command $bg_suffix)
using bg_suffix="&"
I don't know why I am not able comment, but anyway
test $some_var; bg_suffix="&"
would cause bg_suffix to be set regardless of the result of test.

Resources