Bash test with no options appears to work, but why? - bash

I want to pass options to my bash script. If the option "GUI" is set, zenity should be used for input and output instead of the console. Currently I'm passing the option as an environment variable like this:
GUI=1 ./my_bash_script.sh
Then I found out that I could test for the length of the variable like this:
if [ -n "$GUI" ]; then
But then, quite randomly, I discovered that just testing the string with no options also appears to work as expected:
if [ "$GUI" ]; then
I've read the manual entry for test but I can't see any explanation for what happens if you just pass a string without any arguments. I'm guessing that if it receives an empty string it returns true, and otherwise it returns false? Is that the case?

You're correct. From the test(1) man page:
-n STRING
the length of STRING is nonzero
STRING equivalent to -n STRING

Related

How can I only get the boolean value from the return of sysctl, MacOS

I am trying to get the return Boolean value of the command: sysctl debug.lowpri_throttle_enabled, in a shell script (run with zsh).
This command is for knowing whether macOS enables the limit of speed of low-order tasks (i.e. time machine).
Here is the script I have done now:
value=$(sysctl debug.lowpri_throttle_enabled)
echo $value
The output I got is:
>>>debug.lowpri_throttle_enabled: 0
But I just want 0/1 as output, how can I just get the boolean value but not the whole string? Thanks.
I have found the solution. Reference : https://ss64.com/osx/sysctl.html
With adding -n as option, it will only return the value.
e.g.
>>>sysctl -n debug.lowpri_throttle_enabled
0
>>>sudo sysctl debug.lowpri_throttle_enabled=1
>>>sysctl -n debug.lowpri_throttle_enabled
1
According to the description, -n is for:
Show only variable values, not their names. This option is useful for setting shell variables.
And -b is for:
Force the value of the variable(s) to be output in raw, binary format. No names are printed and no terminating newlines are output.
But I don't know why only -n works in this case, -b doesn't.

How does the "read" command work in Bash functions

Just starting to use bash for the first time (so apologies if this is a dumb question).
As far as I've understood, read is used to receive user input. However, in the example below, it seems that it's also being used to assign function arguments.
Am I missing something here? Is there something else going on? I'm finding it hard to find documentation about how this works.
Any help would be appreciated
function server () {
while true
do
read method path version
if $method = 'GET'
then
echo 'HTTP/1.1 200 OK'
else
echo 'HTTP/1.1 400 Bad Request'
fi
done
}
In your example, the read command is used to read input into 3 different variables method, path and version.
If the user enters as input the line "my name is joe", then the values of method, path and version are:
method -> "my"
path -> "name"
version -> "is joe"
If, however, the user enters "hello world", then only method and path will contain a string. Specifically:
method -> "hello"
path -> "world"
version ->
If you want to use the read command to read input into N variables, i.e. read var1 var2 ... varN, the user's input on the command line will be split/ delimited by the characters in the IFS variable. In fact, IFS stands for "Input Field Separators".
By default, the IFS variable is equivalent to $' \t\n'. This means that unless otherwise specified, user inputs are delimited by space, tab or the newline character.
EDIT: IFS is also commonly referred to as the "Internal Field Separator" as David pointed out in the comments (if you see this from other developers).

How to avoid "command not found" in a bash parameter expansion?

I wrote the following bash script:
${MY_FLAG:=true}
${LOG_FILE:="something.log"}
I am trying to assign true to MY_FLAG and the string "something.log" to LOG_FILE. I use parameter expansions because I want to set these variables only if they were not set already.
The problem is that MY_FILE becomes true but LOG_FILE throws an error:
script.sh: line 2: something.log: command not found
I could not find a way to assign the string as is, I tried with different options, simple quotes, and echoing it but nothing did the trick for me.
The parameters will always expand to a value, so you'll have to use them in a context where such an argument is ignored. Conveniently, : aka true does this:
: "${LOG_FILE:="something.log"}"
It only happens to work for your ${MY_FLAG:=true} because true (as discussed) is a valid command. If you run the script with MY_FLAG=date ./yourscript then you'll see that it actually runs date instead of just assigning a default.

Read input, save it to a dynamically-named variable and check if given input was empty

Consider a generic ask() function that asks the user a question, reads the input and saves it in a variable named according to one of the function's arguments.
ask() {
local question="$1"
local varname="$2"
echo "$question"
read $varname
}
Suppose I want to ask the user what is his favourite pet and store the answer in a variable named $pet. Usage would be as follows:
ask "What is your favourite pet?" pet
What I want to do and need help with is check if the user's input was empty, and in that case set the user's input to some string. I would be able to do this easily if the name of the variable the user's input is stored in was constant, like so:
if [ -z "$pet" ]; then
pet="foo"
fi
However the name of the variable I want to check whether or not is empty is whatever I pass in as the second argument. How can I check if the variable (named as per the value of $varname) containing the user's input is empty? The solution should be as portable and standard as possible, and must work under bash and zsh specifically.
In bash, ${!varname} gives you the value of the variable whose name is the value of $varname, but as far as I know, this syntax is not supported by zsh. If you want something that works in both bash and zsh, you may have to use the oldfashioned eval value=\${$varname} and then check $value. You should only use this if you know in advance that the value of $varname is a legal variable name; otherwise this is unsafe.
maybe:
ask() {
name=$1;shift
read -r -p "$# >" var
eval "$name='$var'"
}
ask pet "What is your favourite pet?"
pet=${pet:-foo}
echo "PET: $pet"
Based on the input thus far I managed to get a satisfying solution.
eval varname_tmp=\$$varname
if [ -z "$varname_tmp" ]; then
eval "$varname=foo"
fi

Bash Script Understanding

I'm trying to figure what exactly is the bash code mentioned below trying to do, specially the [-z $M ] part. here M is a variable with a value
if [ -z $M ] ; then
can not find module directory
exit 1
man test Enter
press /-zEnter
you see:
-z STRING
the length of STRING is zero
so your script does, if $M length==0, then exit with status code 1
As others have said, it's using the test command (aka [) to check whether a string is blank. At least, that's what it's trying to do; because the string ($M) isn't double-quoted, it's actually doing something slightly different. Without double-quotes, the value of $M undergoes word splitting and wildcard expansion after it's replaced, so it might not be treated as a simple string (which the -z operator works on) with ... potentially unexpected consequences. Let me run through some of the possibilities:
If the value of $M is a single word (non-blank) without wildcards (* and ?), everything works as expected.
If the value of $M is zero-length (blank), the test command only sees a single argument (-z); when test is only given a single argument, it simply tests whether it's blank -- it's not, so it evaluates to true.
This happens to be the expected result in this case, but it's purely by coincidence, and with many other operators it wouldn't be the right result. For instance, [ -n $M ] (which looks like it should test whether $M is *non*blank), [ -e $M ] (which looks like it should test whether $M is the name of a file/directory) etc will all evaluate to true if $M is blank.
If the value of $M consists entirely of whitespace (but isn't empty), it gets eliminated before test sees it, and test evaluates to true (see previous case). This might or might not be what the scripter had in mind.
If the value of $M has multiple words, test will attempt to evaluate it as (part of) an expression. It will probably not be a valid expression, in which case test will print an error and return false (which is right ... sort of).
On the other hand, if it is a valid expression... Suppose for example you had, M='= -z; test would evaluate the expression -z = -z which would be true, not at all what the scripter had in mind.
If the value of $M has any wildcards, the shell will try to match them against files and pass test the list of matches; it'll try to evaluate them as an expression (see previous case), probably giving an error and returning false (again, sort of right).
Mind you, if you happen to have set the nullglob shell option and the wildcard doesn't match any files, the shell will replace it with null, and the script will act as though "u*n*m*a*t*c*h*e*d" was the empty string.
The lesson here: if you don't want your scripts to behave in weird and unexpected ways, double-quote your variable references!
The [ is actually a standard Unix command (probably implemented internally in Bash, but available whatever shell you are using). It is an alias for the command test, so its manual entry can be found by typing man test. Here's an online copy of that manual page.
When invoked as [, test will generally expect its last argument to be a ], just for good looks, so [ -z $M ] is equivalent to test -z $M.
In this case, the -z argument causes test to return true if the following argument is a string of length zero. The variable $M, defined further up the script, can thus be tested for a valid value.
It checks whether the content of variable M is an empty string.
Check this link

Resources