How to Extract Parts of String in Shell Script into Variables

孤街浪徒 提交于 2019-12-02 11:29:20

Adopting my prior answer to use the heredoc approach suggested by @chepner:

read run failures errors skipped <<EOF
$(grep -E '^Tests run: ' <file.in | tr -d -C '[:digit:][:space:]')
EOF

echo "Tests run: $run"
echo "Failures: $failures"
echo "Errors: $errors"
echo "Skipped: $skipped"

Alternately (put this into a shell function to avoid overriding "$@" for the duration of the script):

unset IFS # assert default values
set -- $(grep -E '^Tests run: ' <in.file | tr -d -C '[:digit:][:space:]')
run=$1; failures=$2; errors=$3; skipped=$4

Note that this is only safe because no glob characters can be present in the output of tr when run in this way; set -- $(something) usually a practice better avoided.


Now, if you were writing for bash rather than POSIX sh, you could perform regex matching internal to the shell (assuming in the below that your input file is relatively short):

#!/bin/bash
re='Tests run: ([[:digit:]]+), Failures: ([[:digit:]]+), Errors: ([[:digit:]]+), Skipped: ([[:digit:]]+)'
while IFS= read -r line; do
  if [[ $line =~ $re ]]; then
    run=${BASH_REMATCH[1]}
    failed=${BASH_REMATCH[2]}
    errors=${BASH_REMATCH[3]}
    skipped=${BASH_REMATCH[4]}
  fi
done <file.in

If your input file is not short, it may be more efficient to have it pre-filtered by grep, thus changing the last line to:

done < <(egrep -E '^Tests run: ' <file.in)

Given the format of the input file, you can capture the output of grep in a here document, then split it with read into four parts to be post-processed.

IFS=, read part1 part2 part3 part4 <<EOF
$(grep '^Tests run' input.txt)
EOF

Then just strip the unwanted prefix from each part.

run=${part1#*: }
failures=${part2#*: }
errors=${part3#*: }
skipped=${part4#*: }

assuming there is only one line starting with Tests run: in your file, and that the file is named foo.txt, the following command will create 4 shell variable that you can work with:

eval $(awk 'BEGIN{ FS="(: |,)" }; /^Tests run/{ print "TOTAL=" $2 "\nFAIL=" $4 "\nERROR=" $6 "\nSKIP=" $8 }' foo.txt); echo $TOTAL; echo $SKIP; echo $ERROR; echo $FAIL

echo $TOTAL; echo $SKIP; echo $ERROR; echo $FAIL is just to demonstrate that the environment variable exists and can be used.

The awk script in a more readable manner is:

BEGIN { FS = "(: |,)" }

/^Tests run/ {
    print "TOTAL=" $2 "\nFAIL=" $4 "\nERROR=" $6 "\nSKIP=" $8
}

FS = "(: |,)" tells awk to consider ":" or "," as field separators.

Then the eval command will read as a command the result of the awk script and as such create the 4 environment variables.


NOTE: due to the use of eval, you must trust the content of the foo.txt file as one could forge a line starting with Tests run: which could have commands thereafter.

You could improve that bit by having a more restrictive regex in the awk script: /^Tests run: \d+, Failures: \d+, Errors: \d+, Skipped: \d+$/

The full command would then be:

eval $(awk 'BEGIN{ FS="(: |,)" }; /^Tests run: \d+, Failures: \d+, Errors: \d+, Skipped: \d+$/{ print "TOTAL=" $2 "\nFAIL=" $4 "\nERROR=" $6 "\nSKIP=" $8 }' foo.txt); echo $TOTAL; echo $SKIP; echo $ERROR; echo $FAIL

There are shorter versions, but this one "shows" each step.

#!/bin/bash
declare -a arr=`grep 'Tests ' a | awk -F',' '{print $1 "\n" $2 "\n" $3 "\n" $4}' | sed 's/ //g' | awk -F':' '{print $2}'`
echo $arr
for var in $arr
do
    echo $var
done
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!