3

In a bash script, I wish to get some numbers from the output of a command and store them in variables. An example of the command output:

25 results [22 valid, 2 invalid, 1 undefined]

I would like to save the four numbers in the previous command-output as variables called results, valid, invalid, undefined.

2
  • No, only spaces in between as shown.
    – nabeelj
    Sep 12, 2018 at 15:06
  • please try below and let me know so I can tweak it if it did not work for you!
    – user88036
    Sep 12, 2018 at 15:07

5 Answers 5

7

Since you want to store multiple separate values, I assume that you want to store them in an array:

$ str='25 results [22 valid, 2 invalid, 1 undefined]'

$ readarray -t arr < <( grep -E -o '[0-9]+' <<<"$str" )

This reads the output of grep into an array called arr. The grep command will output each separate number found in $str on its own line by matching the extended regular expression [0-9]+ against the string and extracting each match. grep reads the string from a "here-string", and readarray reads the grep result using a process substitution.

The values may then be used as

$ printf 'value: %s\n' "${arr[@]}"
value: 25
value: 22
value: 2
value: 1

or, to look at individual values, use e.g. "${arr[0]}", "${arr[1]}", etc. The array contains "${#arr}" values.

results=${arr[0]}
valid=${arr[1]}
invalid=${arr[2]}
undefined=${arr[3]}

To read directly from a command:

readarray -t arr < <( mycommand | grep -E -o '[0-9]+' )
5
  • Thanks, Is there any alternative to the command readarray . I don't have this command in my system! is readarray equivalent to read. Thanks!
    – user88036
    Sep 12, 2018 at 16:01
  • 1
    @Goro Your bash is older than release 4.0 (2009)?
    – Kusalananda
    Sep 12, 2018 at 16:51
  • @ Kusalananda. Thanks for the response. No it is 4.2.46(2)-release
    – user88036
    Sep 12, 2018 at 16:54
  • 1
    @Goro In that case, I see no reason why readarray would not be a valid command, unless you are in fact running some other shell that is not bash.
    – Kusalananda
    Sep 12, 2018 at 17:04
  • 1
    @Goro Ah, just managed to find that the -t option to readarray was added in bash 4.3. It strips the delimiter (newlines by default) off from the read data. Without -t, each array element would have a trailing newline.
    – Kusalananda
    Sep 12, 2018 at 17:06
4

Let's say that the output of this command is saved in a file called output.txt, then you can use the commands awk and grep as follows:

results=$(grep results output.txt | awk '{print $1}')    
valid=$(grep valid output.txt | awk '{print $3}' | tr -d [])
invalid=$(grep invalid output.txt | awk '{print $5}' | tr -d [])
undefined=$(grep undefinedoutput.txt | awk '{print $7}' | tr -d [])

Include these four lines in your bash where it fits.

Instead, you can only use awk to find a matching pattern, as follows:

results=$(awk '/results/{ print $1 }' output.txt)
valid=(awk '/valid/{ print $3 }' output.txt | tr -d [])
invalid=(awk '/invalid/{ print $5 }' output.txt | tr -d [])
undefined=(awk '/undefined/{ print $7 }' output.txt | tr -d [])
4
  • 2
    With the exception of your minor typo ($1 in the second line should be $3}, this works!
    – nabeelj
    Sep 12, 2018 at 15:14
  • The grep seems unnecessary, right? Since the output is already a single line.
    – nabeelj
    Sep 12, 2018 at 15:15
  • 1
    @nabeelj grep + awk almost always means the grep is unnecessary anyway since awk can do pattern matching itsel Sep 12, 2018 at 15:46
  • The grep may be too permissive if any of those words appear elsewhere in the output. Sep 12, 2018 at 19:14
4

Assuming your output is in a variable called output, you can split it with sed, keeping just the spaces and digits, to allow you easily split the "words" into an array:

tim@host:~$ res=($(sed 's/[^0-9 ]*//g' <<< $output))
tim@host:~$ printf "results: %s\nvalid: %s\ninvalid: %s\nundefined: %s\n" "${res[@]}"
results: 25
valid: 22
invalid: 2
undefined: 1
2
  • 1
    Don't put variable expansions into the printf format string. Use placeholders like printf 'results: %s\nvalid: %s\n ...etc..' "${res[@]}"
    – Kusalananda
    Sep 12, 2018 at 15:48
  • is that to protect against variables that contain characters that printf would interpret? Sep 12, 2018 at 20:21
2

You could use parameter expansion to replace everything that's not a digit with a space, then use word splitting to read the result into your variables:

str='25 results [22 valid, 2 invalid, 1 undefined]'
read results valid invalid undefined <<< ${str//[![:digit:]]/ }

The expansion results in something like

$ echo "${str//[![:digit:]]/ }"
25          22        2          1

and read does the rest.

0

On any POSIX shell:

a='aja 25 results [22 valid, 2 invalid, 1 undefined]'

set --                         # clean the list of argumnets.
while [ ${#a} -gt 0 ]; do      # while a is not empty, loop.
    b=${a%%[0-9]*}             # extract leading characters that are not digits (if any).
    a=${a#"$b"}                # remove those from the source variable.
    b=${a%%[^0-9]*}            # extract the leading digits.
    set -- "$@" ${a:+"$b"}     # until a empty, add numbers to the list.
    a=${a#"$b"}                # remove the digits from the source string.
done

printf '<%s> ' "$@"; echo      # print the list of values.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .