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.

  • No, only spaces in between as shown. – nabeelj Sep 12 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 at 15:07
up vote 3 down vote accepted

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 [])
  • 2
    With the exception of your minor typo ($1 in the second line should be $3}, this works! – nabeelj Sep 12 at 15:14
  • The grep seems unnecessary, right? Since the output is already a single line. – nabeelj Sep 12 at 15:15
  • 1
    @nabeelj grep + awk almost always means the grep is unnecessary anyway since awk can do pattern matching itsel – Eric Renouf Sep 12 at 15:46
  • The grep may be too permissive if any of those words appear elsewhere in the output. – Monty Harder Sep 12 at 19:14

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]+' )
  • 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 at 16:01
  • 1
    @Goro Your bash is older than release 4.0 (2009)? – Kusalananda Sep 12 at 16:51
  • @ Kusalananda. Thanks for the response. No it is 4.2.46(2)-release – user88036 Sep 12 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 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 at 17:06

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
  • 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 at 15:48
  • is that to protect against variables that contain characters that printf would interpret? – Tim Kennedy Sep 12 at 20:21

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.

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.

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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