0

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!

user1509130
  • 195
  • 1
  • 2
  • 9
  • you can use extra check by using `if [ -z $my_array[i] ]; then .. . ;fi` statement to check if the variable is empty. – P.... May 28 '19 at 14:32
  • 2
    uninitialized values won't be iterated over : https://ideone.com/c5xtoK. You might want to consider bash arrays as maps (even more since their keys aren't restricted to numeric values) – Aaron May 28 '19 at 14:42
  • 3
    Your example code is not correct bash code. The `$my_array[i]` will expand to the literal `my_array[NUMBER]` because of missing curly braces `{ }`. The `if` is missing a `then`. – Socowi May 28 '19 at 14:54
  • 2
    I don't understand you. The `$my_array[i]` will not work, the `$my_array` is expanded and concatenated with the string `"[i]"`. Did you mean to use `"${my_array[i]}"` ? Can you collaborate more? `my_array[i] is not instantiated ` - can you give an example of "not instatiated" variable in bash? What do you mean by that? You should check if a variable exists and is set using variable expansion, usually `${var+x}` is used, like `[ "${my_array[i]+x}" = x ]`, see [here](https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash). – KamilCuk May 28 '19 at 15:23
  • 3
    It's always a good idea to run your code through http://shellcheck.net/ and fix what it finds before asking questions here. – Charles Duffy May 28 '19 at 16:49
  • You are correct @Socowi I transposed my original code badly. I will fix that in the question. – user1509130 Jun 04 '19 at 21:35
  • Thanks @KamilCuk, see comment above. – user1509130 Jun 04 '19 at 21:36
  • @aaron I have noticed that about the arrays, and it is something that I really like about them. I am not sure if they have the same performance problems that maps do compared to C type arrays (having to search for an index instead of going right to the memory location) but that is another question. – user1509130 Jun 04 '19 at 21:36
  • @PS I do that in a few other places in my code. The problem is that I am wanting an empty value in the array to be treated the same as a false. – user1509130 Jun 04 '19 at 21:36
  • @CharlesDuffy I will do that! Thank you. That is an awesome tool! – user1509130 Jun 04 '19 at 21:37

2 Answers2

1

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 ...
Paul Hodges
  • 13,382
  • 1
  • 17
  • 36
  • This is an interesting idea. the set -u command makes Bash work more how I am used to languages behaving, throwing an error when the variable isn't set. Can you explain what the "${var:-}" is doing in your answer or at give me something I can put into Google to find it? Thank you! – user1509130 Jun 04 '19 at 21:20
  • That's a parameter expansion that explicitly permits a default value to be substituted; using it acknowledges that the variable may be undefined, and thereby prevents `set -u` from taking effect. See https://wiki.bash-hackers.org/syntax/pe#use_a_default_value – Charles Duffy Jun 04 '19 at 21:43
  • You may also find [BashFAQ #112](http://mywiki.wooledge.org/BashFAQ/112) (*What are the advantages and disadvantages of using `set -u` or `set -o nounset`?*) worth reading. – Charles Duffy Jun 04 '19 at 21:45
  • 1
    @user1509130 `"${var:-}"` is similar to my `${array[3]:-unset or empty}` example -- it supplies an explicit replacement if the variable is unset or empty. And that replacement is... the null (zero-length) string. But since there's a replacement specified, it doesn't cause an error even if `-u` is set. – Gordon Davisson Jun 04 '19 at 21:46
  • Gordon nailed it. It uses `set -u` to disallow unset vars, but locally does an explicit bypass on that specific instance of the var to check for an unset. I'm also a big fan of using a `trap` for `ERR` instead of `set -e` so that I can usefully log a controlled exit. – Paul Hodges Jun 05 '19 at 13:06
0

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 ]
Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • Thank you! That answers more than what I was asking. I am new to Bash. Coming from a C derivative background Bash syntax is a bit confusing. I am enjoying it, but it is very different. – user1509130 Jun 04 '19 at 21:16
  • @user1509130 Since you're new to shell, I second Charles Duffy's recommendation of [shellcheck.net](https://www.shellcheck.net). Shell syntax is full of sharp edges and traps for the unwary, and shellcheck is good at pointing out common mistakes. – Gordon Davisson Jun 04 '19 at 21:49