String comparison (Bash)

Uit De Vliegende Brigade
Naar navigatie springen Naar zoeken springen

Detect an unset or null variable

Maybe:

  • unset means that a variable name that has not been declared
  • null means that a variable has not been assigned any value to it (including an empty value).

However, these distinctions aren't very useful, as I don't know how to detect the difference. They seem to be even more academic, as in literature I couldn't find this distinction.

Variables

i1="Hallo"
i2=""
declare i3

[[ -v i1 ]] && echo "	true - i1"   # True: Variable is set (may be empty)
[[ -v i2 ]] && echo "	true - i2"   # True: Variable is set (may be empty)
[[ -v i3 ]] && echo "	true - i3"
[[ -v i4 ]] && echo "	true - i4"

Note that the variables don't include the $ symbol. Appearantly, it's about the variables themselves, not their content.

Copy of an unset variable

This may seem far fetched, but it's quite relevant (for me):

unset i1 i2 i3 i4

i1="hallo"
i3=$i1
i4=""
i5=$i2

[[ -v i1 ]] && echo "	true - i1"	# True: Variable is set (may be empty)
[[ -v i2 ]] && echo "	true - i2"	# False: Variable is not declared and not set
[[ -v i3 ]] && echo "	true - i3"	# True: Variable is set (may be empty)
[[ -v i3 ]] && echo "	true - i3"	# True: Variable is set (may be empty)
[[ -v i4 ]] && echo "	true - i4"	# True: Variable is set (may be empty)
[[ -v i5 ]] && echo "	true - i5"	# False: A copy of an undeclared variable, is undeclared

How this is relevant to me: When going through an associative array of translations, the first step is often that I copy the entities to make-shift variables to make it easier to handle. However, when I do so, I loose the information whether an entity did or didn't exist, and that's important (to know if a translation exists and happens to be "" or if it doesn't exist at all). In the code above, I can't distinguish between the case for i3 and i4.

Associative arrays

This also works for associative arrays:

unset j
declare -gA j

j[one]="Eén"
j[two]=""

[[ -v j[one] ]]   && echo "true - j[one]"     # True: Entity is set and has a value
[[ -v j[two] ]]   && echo "true - j[two]"     # True: Entity is set (may be empty)
[[ -v j[three] ]] && echo "true - j[three]"   # False: The entity is unset

Associative arrays with variable indices

This works:

# Can I detect that j[1,en] is missing?
#
unset j
declare -gA j
i=1

j[$i,nl]="Eén"
j[$i,de]=""

[[ -v j[$i,nl] ]] && echo "true - case 1"   # True: Variable is set (may be empty)
[[ -v j[$i,de] ]] && echo "true - case 2"   # True: Variable is set (may be empty)
[[ -v j[$i,en] ]] && echo "true - case 3"   # False: Variable is not set

Just... whatever

if [ -n "$var" ]; then
  #
  # var has a non-zero length ("-n": "non-zero")
  #
fi

if [ "$var" ]; then
  #
  # var has non-zero length
  #
fi

if [ -z "$var" ]; then
  #
  # var has zero length
  #
fi

if [ ! "$var" ]; then
  #
  # var has zero length
  #
fi

But what is the best way to test for null or for an empty string? Well, that depends. This overview may look impressive, it isn't complete: -v is not included (which uses variable names without $ and that's probably exactly what I need to detect a set from an unset variable:

      1a    2a    3a    4a    5a    6a     |1b    2b    3b    4b    5b    6b
       [     ["    [-n   [-n"  [-z   [-z"  |[[    [["   [[-n  [[-n" [[-z  [[-z"
unset: false false true  false true  true  |false false false false true  true
null : false false true  false true  true  |false false false false true  true
space: false true  true  true  true  false |true  true  true  true  false false
zero : true  true  true  true  false false |true  true  true  true  false false
digit: true  true  true  true  false false |true  true  true  true  false false
char : true  true  true  true  false false |true  true  true  true  false false
hyphn: true  true  true  true  false false |true  true  true  true  false false
two  : -err- true  -err- true  -err- false |true  true  true  true  false false
part : -err- true  -err- true  -err- false |true  true  true  true  false false
Tstr : true  true  -err- true  -err- false |true  true  true  true  false false
Fsym : false true  -err- true  -err- false |true  true  true  true  false false
T=   : true  true  -err- true  -err- false |true  true  true  true  false false
F=   : false true  -err- true  -err- false |true  true  true  true  false false
T!=  : true  true  -err- true  -err- false |true  true  true  true  false false
F!=  : false true  -err- true  -err- false |true  true  true  true  false false
Teq  : true  true  -err- true  -err- false |true  true  true  true  false false
Feq  : false true  -err- true  -err- false |true  true  true  true  false false
Tne  : true  true  -err- true  -err- false |true  true  true  true  false false
Fne  : false true  -err- true  -err- false |true  true  true  true  false false

Voorbeeld (feb. 2016):

#!/bin/bash
sleutel=$1

if [ -z $sleutel ]; then
   echo "Er mankeert iets aan de eerste inputvariabele";
fi

Resultaat:

Invoer          Evaluatie
------          ---------
* Geen invoer   True
* ""            True
* " "           True
* Spaties       True
* Hoihallo      False

Variable value comparison

a=12; [ $a = 12 ]   && echo "12!"   # OK
a=10; [ $a = 12 ]   && echo "12!"   # OK
a=12; [ $a=12 ]     && echo "12!"   # Doesn't work
a=12; [$a=12]       && echo "12!"   # Doesn't work
a=12; [$a = 12]     && echo "12!"   # Error: bash: [12: command not found
a=12; [[ $a = 12 ]] && echo "12!"   # OK

String value comparison

echo ""
echo "### Disable Apache virtual host..."
#
read -p "Continue (y/n)? " continue

if [ "$continue" = "y" ]
then
   echo "Input was 'y'"
else
   echo "Input was not 'y'"
fi

Compare for one letter using regex

Note to self: This is with a regular expression that checks for the existence of just one letter

if [[ "$switches" =~ [f] ]]; then
	echo "f - Create folder structure"
	mappenstructuur=true
fi

if [[ "$switches" =~ [v] ]]; then
	echo "v - Create virtual host file"
	virtueelhostbestand=true
fi

if [[ "$switches" =~ [h] ]]; then
	echo "h - Create /etc/hosts entry"
	voegtoehostbestand=true
fi

if [[ "$switches" =~ [a] ]]; then
	echo "a - Activate Apache"
	activeersite=true
fi

if [[ "$switches" =~ [r] ]]; then
	echo "r - Configure rights on folders & files"
	configureerrechten=true
fi

if [[ "$switches" =~ [b] ]]; then
	echo "b - Create bash alias"
	maakalias=true
fi

Compare for substring (2)

You have to use double '['. Otherwise an error [: too many arguments will follow:

echo "	Path: $path"
if
   [[ "$path"=="/var/www/"* ]]
then
   echo "   Directory exists & contains '/var/www/"
   #
   delete_files
else
   echo "   Directory 'path' not found or doesn't contain '/var/www/' - Skip deleting files"
fi

Multiple clauses

In this case, the second clause needs a double '[' & ']'. The first clause doesn't.

# Delete files
########################################
#
# Let's be extra secure here: Check that the directory exists and that it
# starts with "/var/www/
#
if 
   [ -d "$path" ] && [[ "$path" == "/var/www/"* ]]
then
   delete_files
else
   echo "   Variable 'path' not found or doesn't start with '/var/www/' - Skip deleting files"
fi

Source: https://stackoverflow.com/questions/16203088/bash-if-statement-with-multiple-conditions-throws-an-error

Check for existence of a file

Source: wp_update_site.sh:

# valid "working_dir"?
########################################
#
if [ ! -f "$working_dir/wp-config.php" ]
then
   echo "	File $working_dir/wp-config.php not found. Exiting"
   exit
else
   echo "	Path seems correct"    
fi

Check for existence of a directory

Same example as before:

# Delete files
########################################
#
# Let's be extra secure here: Check that the directory exists and that it
# starts with "/var/www/
#
if 
   [ -d "$path" ] && [[ "$path" == "/var/www/"* ]]
then
   delete_files
else
   echo "   Variable 'path' not found or doesn't start with '/var/www/' - Skip deleting files"
fi

String comparison with regular expressions

Back to an earlier example: The operator =~ indicates that comparison is done using regular expressions.

The wonderful world of regular expressions, and in this case, of Bash regular expressions, deserves its own page: Regular expressions (Bash).

Multiple conditions

This works, but I don't yet understand this syntax:

if [[ "$site_cat" =~ "_bal_" ]] && [[ "$site_cat" =~ "_cb_" ]]
  • Double "[]" needed around both cases
  • This comparison uses regular expressions, and in this specific case, the argument needs to be within ""

Logical or (not tested):

<pre>
if [[ "$site_cat" =~ "_bal_" ]] || [[ "$site_cat" =~ "_cb_" ]]

not + regex (=~)

file: O&O » Bash » compare.sh:

################################################################################
# "=~" (regex) + not
################################################################################
#
# I haven't found a way yet with the negation inside the brackts - That would
# seem more intuitive to me. Anyhow - It works
#
a="blub"
#
echo "1 (a=blub)..."
#
if [[ $a =~ "blub" ]]
then
	echo "a =~ blub"
else
	echo "a not =~ blub"
fi


echo "2 (a=blub)..."
#
if ! [[ $a =~ "blub" ]]
then
	echo "a not =~ blub"
else
	echo "a =~ blub"
fi


a="doei"
#
echo "3 (a=doei)..."
#
if [[ $a =~ "doei" ]]
then
	echo "a =~ doei"
else
	echo "a not =~ doei"
fi


echo "4 (a=doei)..."
#
if ! [[ $a =~ "doei" ]]
then
	echo "a not =~ doei"
else
	echo "a =~ doei"
fi

See also

Sources