How to in-line modify a variable in a shell script? - bash

I have a shell script that sets a variable RESULT= as empty to begin with. Then, the script makes a curl get request if RESULT is empty (if [ -z $RESULT ];then...), and then prints it out, telling the user to set the empty variable.
I am wondering if there is a way for me to in-line modify the RESULT variable only if it is empty, so that afterwards, the variable instead reads a string, such as
RESULT="SUCCESS"

Simply use
: ${RESULT:=$(curl ...)
If RESULT is initially empty or unset, curl will run and its output assigned to RESULT. Otherwise, curl is not run, and RESULT retains whatever value it started with. (Note that RESULT may be an environment variable, with a value before the script actually starts.)
You can extend this to handle arguments as well.
# :-, not :=
RESULT=${1:-$(curl ...)}
curl only runs if the first argument is the empty string or not present:
yourScript
yourScript ""
Otherwise, it assigns whatever the first argument is to RESULT:
yourScript "$(curl ...)"

Supply an assigning default.
if [[ b0Gus == "${RESULT:=b0Gus}" ]]; then... # RESULT now b0Gus if it was empty
This returns the value of RESULT if it has one, else it sets it and returns that. Note that it is more like ((++x)) than ((x++)) in that it applies the change before returning the content to the test operator.
If you use a dash instead of equals, it returns the alternate value, but doesn't set the variable -
if [[ b0Gus == "${RESULT:-b0Gus}" ]]; then... # RESULT still empty after match
See https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html for more.
You can apply this by passing it as args to a no-op, too.
: ${RESULT:=b0Gus}
The : just returns true, but the parser still evaluates its arguments, which will set the var if empty - this is similar to a Perl ||= assignment, though that isn't inline.

Related

less: filter out pattern passed as command line argument + follow file via bash function

I'm trying to create a bash function that will use less to apply a pattern and follow the file using the argument passed to the function
my_less_function() {
if [ -z "$1" ]
then
# if no arg
less +F /var/log/my.log
else
# else, filter out the arg
less +$'&!'$1'\nF' /var/log/my.log
fi
}
my issue is that i can't get the arg to substitute properly in the else block
my_less_function MY_VALUE displays Non-match &/MY_VALUE\nF in less
it looks like it's concatenating the argument and \nF, but \nF is supposed to trigger the follow command instead of being interpreted as part of the argument
any ideas?
wrong : less +$'&!'$1'\nF' /var/log/my.log
right : less +$'&!'${1}$'\nF' /var/log/my.log

Conditional on non-instantiated variable

I am new to Bash scripting, having a lot more experience with C-type languages. I have written a few scripts with a conditional that checks the value of a non-instantiated variable and if it doesn't exist or match a value sets the variable. On top of that the whole thing is in a for loop. Something like this:
for i in ${!my_array[#]}; do
if [ "${my_array[i]}" = true ]
then
#do something
else
my_array[i]=true;
fi
done
This would fail through a null pointer in Java since my_array[i] is not instantiated until after it is checked. Is this good practice in Bash? My script is working the way I designed, but I have learned that just because a kluge works now doesn't mean it will work in the future.
Thanks!
You will find this page on parameter expansion helpful, as well as this one on conditionals.
An easy way to test a variable is to check it for nonzero length.
if [[ -n "$var" ]]
then : do stuff ...
I also like to make it fatal to access a nonexisting variable; this means extra work, but better safety.
set -u # unset vars are fatal to access without exception handling
if [[ -n "${var:-}" ]] # handles unset during check
then : do stuff ...
By default, referencing undefined (or "unset") variable names in shell scripts just gives the empty string. But is an exception: if the shell is run with the -u option or set -u has been run in it, expansions of unset variables are treated as errors and (if the shell is not interactive) cause the shell to exit. Bash applies this principle to array elements as well:
$ array=(zero one two)
$ echo "${array[3]}"
$ echo "array[3] = '${array[3]}'"
array[3] = ''
$ set -u
$ echo "array[3] = '${array[3]}'"
-bash: array[3]: unbound variable
There are also modifiers you can use to control what expansions do if a variable (or array element) is undefined and/or empty (defined as the empty string):
$ array=(zero one '')
$ echo "array[2] is ${array[2]-unset}, array[3] is ${array[3]-unset}"
array[2] is , array[3] is unset
$ echo "array[2] is ${array[2]:-unset or empty}, array[3] is ${array[3]:-unset or empty}"
array[2] is unset or empty, array[3] is unset or empty
There are a bunch of other variants, see the POSIX shell syntax standard, section 2.6.2 (Parameter Expansion).
BTW, you do need to use curly braces (as I did above) around anything other than a plain variable reference. $name[2] is a reference to the plain variable name (or element 0 if it's an array), followed by the string "[2]"; ${name[2]}, on the other hand, is a reference to element 2 of the array name. Also, you pretty much always want to wrap variable references in double-quotes (or include them in double-quoted strings), to prevent the shell from "helpfully" splitting them into words and/or expanding them into lists of matching files. For example, this test:
if [ $my_array[i] = true ]
is (mostly) equivalent to:
if [ ${my_array[0]}[i] = true ]
...which isn't what you want at all. But this one:
if [ ${my_array[i]} = true ]
still doesn't work, because if my_array[i] is unset (or empty) it'll expand to the equivalent of:
if [ = true ]
...which is bad test expression syntax. You want this:
if [ "${my_array[i]}" = true ]

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

How to make a script read a value from a property file and pass it to the same script?

I am new to linux shell script. i want that my script read a property file and save the value in any variable , the same which i can pass in same script..
as i wrote script is not fulfilling my requirement:
!/bin/bash
. test1
flat
if [ "$1" == test1 ]; then
flat=$1; /assign value to var flat
echo "flat"
fi
test1 is property file which includes :
la=12
tu=15
now i want when i run:
./myscript la
it read it from property file and store the value in flat variable.
Please help me.
You just need to use indirect referencing, but to do so, you need to store the value of the special parameter $1 in a regular parameter first.
!/bin/bash
. test1
var="$1"
# Only assign to flat if the variable specified in var is defined
if [ -n "${!var:-}" ]; then
flat="${!var}"; # assign value to var flat
echo "flat"
fi
First, ${!var} expands to the value of the variable whose name is in var. If var is "foo", it's the same as $foo. If var is "baz", it's the same as $baz.
${var:-default} expands to the value of var if it is set and has a non-null value. Otherwise, it expands to whatever you have after the ':-', in this case the string default. If there is no string, it uses the null value. So ${var:-} would expand to the null string if var was not set (or was already the null string).
Combining the two, ${!var:-} takes the variable var, and uses its value as a variable name. It then tries to expand that variable, and if it isn't set or is null, expand to the null string. So if var is la, it expands to the value of la. If var is re, and there is no variable re set, it expands to the null string.
Finally, the -n operator tests if its argument is non-zero length. In other words, it checks that the result of trying to expand the variable whose name is in var is not the null string. If that's true, then expand it again (yes, it's a little redundant) and assign its value to flat.
As the answer is written above, the variable flat is undefined if the argument to the script is not the name of a variable set in test1. If you don't mind flat being set regardless (say, flat=""), you don't need the if statement. You can just use one line to set the value of flat:
#!/bin/bash
. test1
var="$1"
flat="${!var:-}"
If I understood correctly you want to achieve an indirect variable dereferencing (see e.g. this example).
The solution is to use eval:
eval flat=\$$1

Bash Function is not getting called, unless I echo the return value

In my program I am trying to return a value from a function, the return value is string. Everything works fine(atleast some part), if I echo the value once it is returned, but it is not even calling the function, if I dont return.... Consider the code below....
#!/bin/bash
function get_last_name() {
echo "Get Last Name"
ipath=$1
IFS='/'
set $ipath
for item
do
last=$item
done
echo $last
}
main() {
path='/var/lib/iscsi/ifaces/iface0'
current=$(get_last_name "$path")
echo -n "Current="
echo $current
}
main
It gives me an output like this
OUTPUT
Current=Get Last Name iface0
If I comment the echo $current, then the I am not even seeing the "Get Last Name", which makes to come to conclusion, that it is not even calling the function. Please let me know what mistake I am making. But one thing I am sure, bash is the ugliest language I have ever seen.......
Functions do not have return values in bash. When you write
current=$(get_last_name "$path")
you are not assigning a return value to current. You are capturing the standard output of get_last_name (written using the echo command) and assigning it to current. That's why you don't see "Get last name"; that text does not make it to the terminal, but is stored in current.
Detailed explanation
Let's walk through get_last_name first (with some slight modifications to simplify the explanation):
function get_last_name () {
ipath=$1
local IFS='/'
set $ipath
for item
do
last=$item
done
echo "Get Last Name"
echo $last
}
I added the local command before IFS so that the change is confined to the body of get_last_name, and I moved the first echo to the end to emphasize the similarity between the two echo statements. When get_last_name is called, it processes its single argument (a string containing a file path), then echoes two strings: "Get Last Name" and the final component of the file path. If you were to run execute this function from the command line, it would appear something like this:
$ get_last_name /foo/bar/baz
Get Last Name
baz
The exit code of the function would be the exit code of the last command executed, in this case echo $last. This will be 0 as long as the write succeeds (which it almost certainly will).
Now, we look at the function main, which calls get_last_name:
main() {
path='/var/lib/iscsi/ifaces/iface0'
current=$(get_last_name "$path")
echo -n "Current="
echo $current
}
Just like with get_last_name, main will not have a return value; it will produce an exit code which is the exit code of echo $current. The function begins by calling get_last_name inside a command substitution ($(...)), which will capture all the standard output from get_last_name and treat it as a string.
DIGRESSION
Note the difference between the following:
current=$(get_last_name "$path")
sets the value of current to the accumulated standard output of get_last_name. (Among other things, newlines in the output are replaced with spaces, but the full explanation of how whitespace is handled is a topic for another day). This has nothing to do with return values; remember, the exit code (the closet thing bash has to "return values") is a single integer.
current=get_last_name "$path"
would not even call get_last_name. It would interpret "$path" as the name of a command and try to execute it. That command would have a variable current with the string value "get_last_name" in its environment.
The point being, get_last_name doesn't "return" anything that you can assign to a variable. It has an exit code, and it can write to standard output. The $(...) construct lets you capture that output as a string, which you can then (among other things) assign to a variable.
Back to main
Once the value of current is set to the output generated by get_last_name, we execute
two last echo statements to write to standard output again. The first writes "Current=" without a newline, so that the next echo statement produces text on the same line as the first. The second just echoes the value of current.
When you commented out the last echo of main, you didn't stop get_last_name from being executed (it had already been executed). Rather, you just didn't print the contents of the current variable, where the output of get_last_name was placed rather than on the terminal.

Resources