Get from string to variable in bash - bash

I'm writing a bash script to automate some sysadmin stuff. I start with checking that a number of variables are the defined. The way I'm doing that now is like so:
function is_defined {
if [ -z "$2" ]; then
echo "$1 is not defined"
exit
fi
}
is_defined "PROJECTNAME" $PROJECTNAME
What I would love to have is a function that only takes one argument: the variable name as a string, checks that it is defined and if it's not defined tell the user so and exit.
What's the right substitution magic to do this in bash?

Something like this:
function is_defined {
if [ -z "${!1}" ]; then
echo "$1 is not defined"
exit 1
fi
}
${!a} as #sehe already stated will print value of variable, which name is $1

It is possible, with an exotic parameter expansion: ${!var}, which expands to the variable whose name is the $var.
Version 1
is_defined() {
if [ -z "${!1}" ]; then
echo "$1 is not defined"
exit 1
fi
}
But we can simplify it further:
bash has the ${var?errormsg} parameter expansion among its lesser-known features. It basically means "if var is defined, expand to its value; otherwise, print errormsg, set $? nonzero and skip to next command". errormsg is optional, and defaults to parameter null or not set (but the ? is required). As usual with exotic parameter expansions, it can be modified with a colon (${var:?errormsg}) to also error if the variable has an empty value.
In a non-interactive shell, an error generated by this kind of parameter expansion will abort the shellscript.
Version 2
is_defined() {
: ${!1:?"parameter $1 null or unset"}
}
Tested on my MinGW bash just then. The : command just ignores all its input, does nothing, and returns success. (This does have the annoying side-effect of polluting your error message by prefixing it with sh: !1:; use at own desire.)

Related

How can I override a bash variable at the time of executing it in console? [duplicate]

I have a whole bunch of tests on variables in a bash (3.00) shell script where if the variable is not set, then it assigns a default, e.g.:
if [ -z "${VARIABLE}" ]; then
FOO='default'
else
FOO=${VARIABLE}
fi
I seem to recall there's some syntax to doing this in one line, something resembling a ternary operator, e.g.:
FOO=${ ${VARIABLE} : 'default' }
(though I know that won't work...)
Am I crazy, or does something like that exist?
Very close to what you posted, actually. You can use something called Bash parameter expansion to accomplish this.
To get the assigned value, or default if it's missing:
FOO="${VARIABLE:-default}" # If variable not set or null, use default.
# If VARIABLE was unset or null, it still is after this (no assignment done).
Or to assign default to VARIABLE at the same time:
FOO="${VARIABLE:=default}" # If variable not set or null, set it to default.
For command line arguments:
VARIABLE="${1:-$DEFAULTVALUE}"
which assigns to VARIABLE the value of the 1st argument passed to the script or the value of DEFAULTVALUE if no such argument was passed. Quoting prevents globbing and word splitting.
If the variable is same, then
: "${VARIABLE:=DEFAULT_VALUE}"
assigns DEFAULT_VALUE to VARIABLE if not defined.
The colon builtin (:) ensures the variable result is not executed
The double quotes (") prevent globbing and word splitting.
Also see Section 3.5.3, Shell Parameter Expansion, in the Bash manual.
To answer your question and on all variable substitutions
echo "${var}"
echo "Substitute the value of var."
echo "${var:-word}"
echo "If var is null or unset, word is substituted for var. The value of var does not change."
echo "${var:=word}"
echo "If var is null or unset, var is set to the value of word."
echo "${var:?message}"
echo "If var is null or unset, message is printed to standard error. This checks that variables are set correctly."
echo "${var:+word}"
echo "If var is set, word is substituted for var. The value of var does not change."
You can escape the whole expression by putting a \ between the dollar sign and the rest of the expression.
echo "$\{var}"
Even you can use like default value the value of another variable
having a file defvalue.sh
#!/bin/bash
variable1=$1
variable2=${2:-$variable1}
echo $variable1
echo $variable2
run ./defvalue.sh first-value second-value output
first-value
second-value
and run ./defvalue.sh first-value output
first-value
first-value
see here under 3.5.3(shell parameter expansion)
so in your case
${VARIABLE:-default}
FWIW, you can provide an error message like so:
USERNAME=${1:?"Specify a username"}
This displays a message like this and exits with code 1:
./myscript.sh
./myscript.sh: line 2: 1: Specify a username
A more complete example of everything:
#!/bin/bash
ACTION=${1:?"Specify 'action' as argv[1]"}
DIRNAME=${2:-$PWD}
OUTPUT_DIR=${3:-${HOMEDIR:-"/tmp"}}
echo "$ACTION"
echo "$DIRNAME"
echo "$OUTPUT_DIR"
Output:
$ ./script.sh foo
foo
/path/to/pwd
/tmp
$ export HOMEDIR=/home/myuser
$ ./script.sh foo
foo
/path/to/pwd
/home/myuser
$ACTION takes the value of the first argument, and exits if empty
$DIRNAME is the 2nd argument, and defaults to the current directory
$OUTPUT_DIR is the 3rd argument, or $HOMEDIR (if defined), else, /tmp. This works on OS X, but I'm not positive that it's portable.
Then there's the way of expressing your 'if' construct more tersely:
FOO='default'
[ -n "${VARIABLE}" ] && FOO=${VARIABLE}
It is possible to chain default values like so:
DOCKER_LABEL=${GIT_TAG:-${GIT_COMMIT_AND_DATE:-latest}}
i.e. if $GIT_TAG doesnt exist, take $GIT_COMMIT_AND_DATE - if this doesnt exist, take "latest"
Here is an example
#!/bin/bash
default='default_value'
value=${1:-$default}
echo "value: [$value]"
save this as script.sh and make it executable.
run it without params
./script.sh
> value: [default_value]
run it with param
./script.sh my_value
> value: [my_value]
If you want 1 liner for your if-then-else, then we can consider rewriting:
if [ -z "${VARIABLE}" ]; then
FOO='default'
else
FOO=${VARIABLE}
fi
with semicolons:
if [ -z ${VARIABLE} ]; then FOO=`default`; else FOO=${VARIABLE}; fi
Alternatively, you can drop the if-then-else-fi keywords if you use boolean operators such as:
[ -z "${VARIABLE}" ] && FOO='default' || FOO=${VARIABLE}
Generalizing, the boolean operator pattern is:
conditional && then_command || else_command

`set -u` (nounset) vs checking whether I have arguments

I'm trying to improve this nasty old script. I found an undefined variable error to fix, so I added set -u to catch any similar errors.
I get an undefined variable error for "$1", because of this code
if [ -z "$1" ]; then
process "$command"
It just wants to know if there are arguments or not. (The behaviour when passed an empty string as the first argument is not intended. It won't be a problem if we happen to fix that as well).
What's a good way to check whether we have arguments, when running with set -u?
The code above won't work if we replace "$1" with "$#", because of the special way "$#" is expanded when there is more than one argument.
$# contains the number of arguments, so you can test for $1, $2, etc. to exist before accessing them.
if (( $# == 0 )); then
# no arguments
else
# have arguments
fi;
You can ignore the automatic exit due to set -u by setting a default value in the parameter expansion:
#!/bin/sh
set -u
if [ -z "${1-}" ] ; then
echo "\$1 not set or empty"
exit 1
fi
echo "$2" # this will crash if $2 is unset
The syntax is ${parameter-default}, which gives the string default if the named parameter is unset, and the value of parameter otherwise. Similarly, ${parameter:-default} gives default if the named parameter is unset or empty. Above, we just used an empty default value. (${1:-} would be the same here, since we'd just change an empty value to an empty value.)
That's a feature of the POSIX shell and works with other variables too, not just the positional parameters.
If you want to tell the difference between an unset variable and an empty value, use ${par+x}:
if [ "${1+x}" != x ] ; then
echo "\$1 is not set"
fi
My personal favorite :
if
(($#))
then
# We have at least one argument
fi
Or :
if
((!$#))
then
# We have no argument
fi
If each positional argument has a fixed meaning, you can also use this construct:
: ${1:?Missing first argument}
If the first positional argument isn't set, the shell will print "Missing first argument" as an error message and exit. Otherwise, the rest of the script can continue, safe in the knowledge the $1 does, indeed, have a non-empty value.
Use $#, the number of arguments. This provides the most consistent handling for empty arguments.
You might also see the use of "$*". It is similar to "$#", but it is expanded differently when there are multiple arguments.
a() {
for i in "$#"; do echo $i; done
}
b() {
for i in "$*"; do echo $i; done
}
c() {
echo $#
}
echo "a()"
a "1 2" 3
echo "b()"
b "1 2" 3
echo "c()"
c "1 2" 3
# Result:
a()
1 2
3
b()
1 2 3
c()
2

How to call and pass arguments to functions with spaces in unix

I'm creating a unix script that will call and pass arguments to function in unix. Once called, the function should identify how many parameters passed to it. I tried the normal way of calling and passing of arguments to function and it works. However, I noticed that the function is counting the arguments word by word and my problem on it is that, what if I have a single argument that contains spaces or a multiple arguments but some of it should be single argument but with spaces? Is it possible to identify by the function that the arguments specified should be considered as single argument? I already used double quotation and it didn't work.
Here is the relevant portion of my script.
#!/usr/bin/ksh
ARG_CNT() {
SCRIPT_AR_CNT=$#
if [ SCRIPT_AR_CNT -lt 3 ]; then
echo "Error. Incorrect number of arguments specified."
echo "Error. Execute \"./script_template.ksh -h\" for help."
exit 1
fi
}
echo "Specify the Arguments: "
read SCRIPT_AR
if [ "${SCRIPT_AR}" = "" ] || [ "${SCRIPT_AR}" = "." ]; then
exit
else
ARG_CNT $SCRIPT_AR
fi
Your problem is that you're not quoting your variables:
ARG_CNT $SCRIPT_AR
If you don't quote regular variables, they'll be split on $IFS. You should only leave out quotes if you explicitly want this kind of splitting, and that should be rare (so comment it). Quoting also slightly improves performance.
ARG_CNT "$SCRIPT_AR"
If I may suggest more edits:
#!/usr/bin/ksh
arg_cnt() {
#ALL_CAPS should be reserved to env variables (exported vars) and shell config variables
script_ar_cnt=$#
[ script_ar_cnt -lt 3 ] && {
echo "Error. Incorrect number of arguments specified."
echo "Error. Execute \"./script_template.ksh -h\" for help."
exit 1
} >&2
}
echo "Specify the Arguments: "
read script_ar
ex_dataerr=65 # data format error
{ [ -z "$script_ar" ] || [ "$script_ar" = "." ]; } && exit "$ex_dataerr"
arg_cnt "$script_ar"

How to test if a variable exists and has been initialized

I have to execute a function which has to test if a variable has been correctly defined in Bash and must use its associated value.
For instance, these variables are initialized at the top of the script.
#!/bin/bash
var1_ID=0x04
var2_ID=0x05
var3_ID=0x06
var4_ID=0x09
I would like to call the script named test as follows:
./test var1
The current implemented function is:
function Get()
{
if [ $1"_ID" != "" ]; then
echo "here"
echo $(($1_ID))
else
exit 0
fi
}
I don't understand why I obtain here even if I enter ./test toto or something else.
Do I need to use a specific command, such as grep?
Use parameter expansion:
: ${var:?}
Remove the colon if the empty string is a valid value (i.e., you only want to test for definedness).
: ${var?}
If you don't want the script to stop on the problem, you can use
if [[ ${var:+1} ]] ; then
# OK...
else
echo Variable empty or not defined. >&2
fi
Documented under Parameter Expansion in man bash:
When not performing substring expansion, using the forms documented below (e.g., :-), bash
tests for a parameter that is unset or null. Omitting the colon results in a test only
for a parameter that is unset.
${parameter:?word}
Display Error if Null or Unset. If parameter is null or unset, the expansion of
word (or a message to that effect if word is not present) is written to the standard error and the shell, if it is not interactive, exits. Otherwise, the value of
parameter is substituted.
${parameter:+word}
Use Alternate Value. If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.
You probably want to use indirect expansion: ${!variable} and then -n to check if it has been defined:
The indirect expansion consists in calling a variable with another variable. That is, as the variable name may be changing, instead of saying $a we say ${!var} and var=a.
$ cat a
var1_ID=0x04
var2_ID=0x05
var3_ID=0x06
var4_ID=0x09
for i in {1..5}; do
v="var${i}_ID"
if [ -n "${!v}" ]; then # <-- this expands to varX_ID
echo "$v set to value: ${!v}"
else
echo "$v not set"
fi
done
If we execute, we get:
$ ./a
var1_ID set to value: 0x04
var2_ID set to value: 0x05
var3_ID set to value: 0x06
var4_ID set to value: 0x09
var5_ID not set
From man test:
-n STRING
the length of STRING is nonzero
In bash 4.2, you can use the -v operator in a conditional expression to test if a variable with the given name is set.
if [[ -v ${1}_ID ]]; then
echo "${1}_ID is set"
foo=${1}_ID
echo "${!foo}"
fi
You still need indirect parameter expansion to get the value.
In bash 4.3 you can use a named reference to make working with it easier.
declare -n param=${1}_ID
if [[ -v param ]]; then
echo "${1}_ID"
echo "$param"
fi
(param will behave exactly like the variable it references. I don't know if there is an easy way, short of parsing the output of declare -p param, to get the name of the variable it references.)

Assigning default values to shell variables with a single command in bash

I have a whole bunch of tests on variables in a bash (3.00) shell script where if the variable is not set, then it assigns a default, e.g.:
if [ -z "${VARIABLE}" ]; then
FOO='default'
else
FOO=${VARIABLE}
fi
I seem to recall there's some syntax to doing this in one line, something resembling a ternary operator, e.g.:
FOO=${ ${VARIABLE} : 'default' }
(though I know that won't work...)
Am I crazy, or does something like that exist?
Very close to what you posted, actually. You can use something called Bash parameter expansion to accomplish this.
To get the assigned value, or default if it's missing:
FOO="${VARIABLE:-default}" # If variable not set or null, use default.
# If VARIABLE was unset or null, it still is after this (no assignment done).
Or to assign default to VARIABLE at the same time:
FOO="${VARIABLE:=default}" # If variable not set or null, set it to default.
For command line arguments:
VARIABLE="${1:-$DEFAULTVALUE}"
which assigns to VARIABLE the value of the 1st argument passed to the script or the value of DEFAULTVALUE if no such argument was passed. Quoting prevents globbing and word splitting.
If the variable is same, then
: "${VARIABLE:=DEFAULT_VALUE}"
assigns DEFAULT_VALUE to VARIABLE if not defined.
The colon builtin (:) ensures the variable result is not executed
The double quotes (") prevent globbing and word splitting.
Also see Section 3.5.3, Shell Parameter Expansion, in the Bash manual.
To answer your question and on all variable substitutions
echo "${var}"
echo "Substitute the value of var."
echo "${var:-word}"
echo "If var is null or unset, word is substituted for var. The value of var does not change."
echo "${var:=word}"
echo "If var is null or unset, var is set to the value of word."
echo "${var:?message}"
echo "If var is null or unset, message is printed to standard error. This checks that variables are set correctly."
echo "${var:+word}"
echo "If var is set, word is substituted for var. The value of var does not change."
You can escape the whole expression by putting a \ between the dollar sign and the rest of the expression.
echo "$\{var}"
Even you can use like default value the value of another variable
having a file defvalue.sh
#!/bin/bash
variable1=$1
variable2=${2:-$variable1}
echo $variable1
echo $variable2
run ./defvalue.sh first-value second-value output
first-value
second-value
and run ./defvalue.sh first-value output
first-value
first-value
see here under 3.5.3(shell parameter expansion)
so in your case
${VARIABLE:-default}
FWIW, you can provide an error message like so:
USERNAME=${1:?"Specify a username"}
This displays a message like this and exits with code 1:
./myscript.sh
./myscript.sh: line 2: 1: Specify a username
A more complete example of everything:
#!/bin/bash
ACTION=${1:?"Specify 'action' as argv[1]"}
DIRNAME=${2:-$PWD}
OUTPUT_DIR=${3:-${HOMEDIR:-"/tmp"}}
echo "$ACTION"
echo "$DIRNAME"
echo "$OUTPUT_DIR"
Output:
$ ./script.sh foo
foo
/path/to/pwd
/tmp
$ export HOMEDIR=/home/myuser
$ ./script.sh foo
foo
/path/to/pwd
/home/myuser
$ACTION takes the value of the first argument, and exits if empty
$DIRNAME is the 2nd argument, and defaults to the current directory
$OUTPUT_DIR is the 3rd argument, or $HOMEDIR (if defined), else, /tmp. This works on OS X, but I'm not positive that it's portable.
Then there's the way of expressing your 'if' construct more tersely:
FOO='default'
[ -n "${VARIABLE}" ] && FOO=${VARIABLE}
It is possible to chain default values like so:
DOCKER_LABEL=${GIT_TAG:-${GIT_COMMIT_AND_DATE:-latest}}
i.e. if $GIT_TAG doesnt exist, take $GIT_COMMIT_AND_DATE - if this doesnt exist, take "latest"
Here is an example
#!/bin/bash
default='default_value'
value=${1:-$default}
echo "value: [$value]"
save this as script.sh and make it executable.
run it without params
./script.sh
> value: [default_value]
run it with param
./script.sh my_value
> value: [my_value]
If you want 1 liner for your if-then-else, then we can consider rewriting:
if [ -z "${VARIABLE}" ]; then
FOO='default'
else
FOO=${VARIABLE}
fi
with semicolons:
if [ -z ${VARIABLE} ]; then FOO=`default`; else FOO=${VARIABLE}; fi
Alternatively, you can drop the if-then-else-fi keywords if you use boolean operators such as:
[ -z "${VARIABLE}" ] && FOO='default' || FOO=${VARIABLE}
Generalizing, the boolean operator pattern is:
conditional && then_command || else_command

Resources