1

I'm trying to read a line of text in bash using grep, piped to tail to get the final line of the file, and then slice the first three "words" (i.e. dividing them using space) of that line as elements of an array.

It works fine if I try to, e.g. loop over the elements in the output using a for loop, and I get the list of elements I want:

[] foo=$(grep select file.txt | tail -n 1)
[] echo $foo
0.47331 5.11188 13.1615 # select

[] for x in $foo; do echo $x; done
0.47331
5.11188
13.1615
#
select

Exactly what I want it to do!

But if I try to get out an array with the first three elements of foo, I cannot get it to work:

[] echo "${foo[@]:0:2}"
0.47331 5.11188 13.1615 # select 4.95294 13.5177

What's particularly weird is that those last two values at the end of the line are actually two values from the first line containing select in file.txt (and not even the first two items on that line, but the second and third!), so they shouldn't even be part of foo at all...

Similarly, if I try and simply slice a single "word" from foo, I get a weird output:

[] echo "${foo[0]}"
0.47331 5.11188 13.1615 # select

[] echo "${foo[1]}"
4.95294

(Again, that last value is a value that shouldn't, as I best understand it, even be in foo, it's the second item on the first line with select in file.txt...).

I need to understand what is going on, and how to get out the output I want, namely an array 0.47331 5.11188 13.1615.

  • if you want us to help, please could you provide a simple example of your issue, with input/output/expected output and describe what is not working simply ? – Kiwy Feb 5 at 8:40
  • 2
    Does foo=( $(grep select file.txt | tail -n 1) ) solve all your problems? – Michael Homer Feb 5 at 8:45
  • echo "${foo[@]:0:2}" ... you haven't created a foo array yet, so what did you expect this to do? – Olorin Feb 5 at 8:46
3

$foo isn't an array; it's a string containing several space-separated words. If you want it to be an array, assign it as one:

foo=( $(grep 'select' file.txt | tail -n 1) )

Now you can reference ${foo[0]}, or the first three elements as ${foo[@]:0:3}.

Note that ${foo[@]} will contain all five elements from your last matching line as there's nothing here to extract just the first three elements. You could use foo=("${foo[@]:0:3}") to chop ${foo[@]} down to size if you wanted.

Alternatively, if you want to create the array with just the first three elements you can use awk like this:

foo=( $(awk '/select/ { a=$1; b=$2; c=$3 } END { print a,b,c }' file.txt) )
  • Thanks, that does work, and I can then use ${foo[@]:0:3} to get the first three elements. But do you know how the script above is pulling elements that I didn't think would even be in foo? – Henry Brice Feb 5 at 8:48
  • The array is made from all the space-separated elements. There was nothing in your code to pick out just the first three. – roaima Feb 5 at 8:52
  • But given that foo was defined as the output of the grep piped to tail, how does the slicing call get the values that are in file.txt? Or am I wrong, and foo actually somehow contains all of the grep call, even though the for loop doesn't see it? – Henry Brice Feb 5 at 10:25
  • Your $foo was a string containing the result of your grep | tail. It had five space-separated elements. We can force that as an array with the foo=( ... ) construct, but it still has five elements. You can pick out the first three with ${foo[@]:0:3}. I've updated my answer to incorporate this. – roaima Feb 5 at 11:25
1

This avoids reading the data into variables:

$ grep -F select file | tail -n 1 | cut -d ' ' -f 1-3
0.47331 5.11188 13.1615

To get the values into an array in bash, use mapfile (or readarray):

$ mapfile -t arr < <( grep -F select file | tail -n 1 | cut -d ' ' -f 1-3 | tr ' ' '\n' )
$ printf 'arr: %s\n' "${arr[@]}"
arr: 0.47331
arr: 5.11188
arr: 13.1615

I'm using tr to change the spaces between the numbers to newlines. That way I don't get a trailing newline character at the end of the last value in the array.


Instead of the awkward grep+tail+cut+tr pipeline, you could obviously use

awk '/select/ { a=$1; b=$2; c=$3 } END { printf("%s\n%s\n%s\n", a, b, c) }'

or something like it inside the <(...) process substitution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.