How to test if a command is a shell reserved word? - bash

I am writing a bash script and I would like to verify if a string is a shell reserved word (like if, for, alias, etc...).
How can I do this?

#!/bin/bash
string="$1"
if [[ $(type "$string" 2>&1) == "$string is a shell"* ]]; then
echo "Keyword $string is reserved by shell"
fi

If you want only the shell keywords, then:
#!/bin/bash
string="$1"
[[ $(type -t "$string" 2>&1) == "keyword" ]] && echo reserved || echo not reserved
builtins won't pass this test (only keywords).
A way of doing this with possibility of extending in various cases:
#!/bin/bash
string="$1"
checkfor=('keyword' 'builtin')
for ((i=0;i<${#checkfor[#]};i++))
do
[[ $(type -t "$string" 2>&1) == "${checkfor[$i]}" ]] && reserved=true && break || reserved=false
done
[[ $reserved == true ]] && echo reserved || echo not reserved
command, hash, alias, type etc.. (builtins) will pass the above test as well as keywords.
You can add other possible test conditions by adding an element into the array checkfor:
checkfor=('keyword' 'builtin' 'file' etc...)

Related

unset variable check in bash

I have two variables declared but unset:
__var1=
__var2=
Now I set __var2 to have some value:
__var2=1
When I try to do a check like this:
[ -z "$__var1" -a -z "$__var2" ] || echo "Both missing!"
I am getting that message Both missing!. But that's incorrect.
Why is that? And how to do a proper check, to see if both of them are missing?
And if the user wants to check if the variable is really unset and not just having an empty value, you can do:
$ A=1234
$ [[ -z ${A+.} ]] && echo "Variable is unset."
$ A=
$ [[ -z ${A+.} ]] && echo "Variable is unset."
$ unset A
$ [[ -z ${A+.} ]] && echo "Variable is unset."
Variable is unset.
In which in your case it could be
[[ -z ${__var1+.} && -z ${__var2+.} ]] && echo "Both variables are unset!"
#Dave Schweissguth's answer makes a good point about the logic of your code, but there are also things to observe about the syntax:
[Update: The original form of the question used assignments such as $__var1= - this has since been corrected] In Bourne-like/POSIX-compatible shells you do not use the $ prefix when assigning a value, only when referencing it; thus, your assignments should read:
__var1=
__var2= # or, later: __var2=1
Your question is tagged bash, so the best bash way to write your could would be:
[[ -z $__var1 && -z $__var2 ]] && echo "Both missing!"
Note the use of [[ ... ]] rater than [ ... ], which obviates the need to double-quote the operands to -z.
By contrast, the most portable (POSIX-compliant) way is:
[ -z "$__var1" ] && [ -z "$__var2" ] && echo "Both missing!"
Your code prints "Both missing!" if it's not true (||) that both (-a) variables are empty (-z). You want to print the message if that IS true. Do that like this:
[ -z "$__var1" -a -z "$__var2" ] && echo "Both missing!"
I don't recall ever seeing a version of bash or test (what sh uses to evaluate the same expressions) without -z or -a, so as far as I know the above will work on any Unix-like system you're likely to find.

whats the best practice for conditionals in bash when checking multiple statements?

Given your trying to check that a variable is not empty and not some other value as in the following code:
if [ ! -z "$foo" ] && [[ ${foo} != "bar" ]]; then
what is the best practice for accomplishing this. I've seen bash conditionals written several ways including the following...
if [[ ! -z "$foo" && ${foo} != "bar" ]]; then
I understand there is a difference when using the single brackets and the double, I'm more concerned with when to put the && or || inside the brackets or out.
Put &&/|| inside brackets for [[ ]]. Outside is also accepted.
Put &&/|| outside brackets for [ ]. Inside is NOT allowed.
This is due to the fact that && binds normal commands together based on return value, e.g.
wget file && echo "Success"
[, despite its funny name, is a regular command and obeys the same rules as e.g. wget or echo.
[ foo || bar ] is two commands, [ foo and bar ], neither of which are valid.
[[ .. ]] on the other hand is not a normal command but special shell syntax. [[ foo || bar ]] is a single command, and interpretted accordingly.
To complete the previous answers :
if [[ ! -z $foo && $foo != "bar" ]]; then ...
# [[ will execute the two conditions with "and" operator in a single instruction
Is equivalent of :
if [[ ! -z $foo -a $foo != "bar" ]]; then ...
# [[ will execute the two conditions with "and" operator in a single instruction
But not equivalent of :
if [[ ! -z $foo ]] && [[ $foo != "bar" ]]; then ...
# second [[ will be executed if the first success ($? = 0)
-a (and) and -o (or) will work with test and [.
See man test to get more details ;)
Otherwise, no need to protect your variables by doubles quotes with [[ and no need to use delimiters (${}) in this case.
Here is a reminder about the necessity (or not) to protect your variables with double quotes..

Compare bash variable

I need help with how to compare bash variable to a specific format.
i will read user input with read command
for example:
MyComputer:~/Home$ read interface
eth1
MyComputer:~/Home$ echo $interface
eth1
Now i need to check if "$interface" variable with IF loop(it should have "eth" in beginning and should contains numbers 0-9):
if [[ $interface=^eth[0-9] ]]
then
echo "It looks like an interface name"
fi
Thanks in advance
You can use regular expressions for this:
if [[ $interface =~ ^eth[0-9]+$ ]]
then
...
fi
You can use bash's globs for this:
if [[ $interface = eth+([[:digit:]]) ]]; then
echo "It looks like an interface name"
fi
(avoiding regexps removes one problem). Oh, and mind the spaces around the = sign, and also before and after [[ and ]].
You could use bash V3+ operator =~ as Andrew Logvinov said :
[[ $interface =~ ^eth[0-9]+$ ]] && # ...
Or :
if [[ $interface =~ ^eth[0-9]+$ ]]; then
# ...
fi
Otherwise, you could use too egrep or grep -E (which is useful with older shells like sh...) :
echo "$interface"|egrep "^eth[0-9]+$" > /dev/null && # ...
Or :
if echo "$interface"|egrep "^eth[0-9]+$" > /dev/null; then
# ...
fi

bash - Possible to 'override' the test ([[)-builtin?

Is it possible to override Bash's test builtin? So that
[[ $1 = 'a' ]]
not just does the test but also outputs which result was expected when it fails? Something like
echo "Expected $1 to be a.'
EDIT
I know this is bad :-).
The test expression compound command does real short-circuiting that affects all expansions.
$ set -x
$ [[ 0 -gt x=1+1 || ++x -eq $(tee /dev/fd/3 <<<$x) && $(echo 'nope' >&3) ]] 3>&1
+ [[ 0 -gt x=1+1 ]]
++ tee /dev/fd/2
2
+ [[ ++x -eq 2 ]]
So yes you could do anything in a single test expression. In reality it's quite rare to have a test produce a side-effect, and almost never used to produce output.
Also yes, reserved words can be overridden. Bash is more lenient with ksh-style function definitions than POSIX style (which still allows some invalid names).
function [[ { [ "${#:1:${##}-1}" ]; }; \[[ -a -o -a -o -a ]] || echo lulz
Yet another forky bomb.
if function function if function if if \function & then \if & fi && \if & then \function & fi && then \function fi
Something like this?
if [[ $1 == 'a' ]]; then
echo "all right";
else
echo 'Expected $1 to be "a"'
fi
Anyway, what's the point of the test if you only expect one answer? Or do you mean that for debugging purposes?
[[ 'a' = 'a' ]] || echo "failed"
[[ 'b' = 'a' ]] || echo "failed"
failed

Escaping in test comparisons

In the following, I would like check if a given variable name is set:
$ set hello
$ echo $1
hello
$ echo $hello
$ [[ -z \$$1 ]] && echo true || echo false
false
Since $hello is unset, I would expect the test to return true. What's wrong here? I would assume I am escaping the dollar incorrectly.
TYIA
You are testing if \$$1 is empty. Since it begins with a $, it is not empty. In fact, \$$1 expands to the string $hello.
You need to tell the shell that you want to treat the value of $1 as the name of a parameter to expand.
With bash: [[ -z ${!1} ]]
With zsh: [[ -z ${(P)1} ]]
With ksh: tmp=$1; typeset -n tmp; [[ -z $tmp ]]
Portably: eval "tmp=\$$1"; [ -z "$tmp" ]
(Note that these will treat unset and empty identically, which is usually the right thing.)

Resources