Tuesday, January 30, 2024

[SOLVED] Unable to add element to array in bash

Issue

I have the following problem. Let´s assume that $@ contains only valid files. Variable file contains the name of the current file (the file I'm currently "on"). Then variable element contains data in the format file:function.

Now, when variable element is not empty, it should be put into the array. And that's the problem. If I echo element, it contains exactly what I want, although it is not stored in array, so for cycle doesn't print out anything.

I have written two ways I try to insert element into array, but neither works. Can you tell me, What am I doing wrong, please?

I'm using Linux Mint 16.

#!/bin/bash

nm $@ | while read line
do
  pattern="`echo \"$line\" | sed -n \"s/^\(.*\):$/\1/p\"`"
  if [ -n "$pattern" ]; then
    file="$pattern"  
  fi
  element="`echo \"$line\" | sed -n \"s/^U \([0-9a-zA-Z_]*\).*/$file:\1/p\"`"
  if [ -n "$element" ]; then
    array+=("$element")
    #array[$[${#array[@]}+1]]="$element"
    echo element - "$element"
  fi
done

for j in "${array[@]}"
do
  echo "$j"
done

Solution

Your problem is that the while loop runs in a subshell because it is the second command in a pipeline, so any changes made in that loop are not available after the loop exits.

You have a few options. I often use { and } for command grouping:

nm "$@" |
{
while read line
do
    …
done
for j in "${array[@]}"
do
    echo "$j"
done
}

In bash, you can also use process substitution:

while read line
do
    …
done < <(nm "$@")

Bash also supports an option shopt -s lastpipe which runs the last process in a pipeline in the current shell environment so that changes made by the last part of the pipeline are available to the rest of the script. Then you do not need the command grouping notation or process substitution.


Also, it is better to use $(…) in place of back-quotes `…` (and not just because it is hard work getting back quotes into markdown text!).

Your line:

element="`echo \"$line\" | sed -n \"s/^U \([0-9a-zA-Z_]*\).*/$file:\1/p\"`"

could be written:

element="$(echo "$line" | sed -n "s/^U \([0-9a-zA-Z_]*\).*/$file:\1/p")"

or even:

element=$(echo "$line" | sed -n "s/^U \([0-9a-zA-Z_]*\).*/$file:\1/p")

It really helps when you need to nest command substitution operations. For example, to list the lib directory adjacent to where gcc is found:

ls -l $(dirname $(dirname $(which gcc)))/lib

vs

ls -l `dirname \`dirname \\\`which gcc\\\`\``/lib

I know which I find easier!



Answered By - Jonathan Leffler
Answer Checked By - Cary Denson (WPSolving Admin)