Regex 如何将Shell脚本中的字符串部分提取到变量中

Regex 如何将Shell脚本中的字符串部分提取到变量中,regex,shell,sh,Regex,Shell,Sh,我正在尝试在sh中执行以下操作 这是我的档案: foo bar Tests run: 729, Failures: 0, Errors: 253, Skipped: 0 baz 如何将这4个数字转换成4个不同的变量?我现在花了大约一个小时在sed和awk手册页上,我正在转动我的轮子。采用我先前的答案,使用@chepner建议的heredoc方法: read run failures errors skipped <<EOF $(grep -E '^Tests run: ' <

我正在尝试在sh中执行以下操作

这是我的档案:

foo
bar
Tests run: 729, Failures: 0, Errors: 253, Skipped: 0
baz

如何将这4个数字转换成4个不同的变量?我现在花了大约一个小时在sed和awk手册页上,我正在转动我的轮子。

采用我先前的答案,使用@chepner建议的heredoc方法:

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"
如果您的输入文件不短,则通过grep对其进行预筛选可能更有效,从而将最后一行更改为:

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

done<有较短的版本,但这一版本“显示”了每个步骤

#!/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

假设文件中只有一行以
Tests run:
开头,并且文件名为
foo.txt
,则以下命令将创建4个shell变量,您可以使用该变量:

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$错误;echo$FAIL
只是为了证明环境变量存在并且可以使用

更具可读性的awk脚本是:

BEGIN{FS=“(:|,)”}
/^测试运行/{
打印“TOTAL=“$2”\n邮件=“$4”\n错误=“$6”\nSKIP=“$8”
}

<代码> FS=“(:,)”告诉AWK考虑“<代码>:<代码> >或“<代码>,<代码> >作为字段分隔符。

然后
eval
命令将作为命令读取awk脚本的结果,从而创建4个环境变量


注意:由于使用了
eval
,您必须信任
foo.txt
文件的内容,因为您可以伪造一行以
Tests run:
开头的命令

您可以通过在awk脚本中使用更严格的正则表达式来改进这一点:
/^测试运行:\d+,失败:\d+,错误:\d+,跳过:\d+$/

然后,完整命令将是:

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

给定输入文件的格式,您可以在here文档中捕获
grep
的输出,然后使用
read
将其拆分为四个部分进行后处理

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

文件的确切格式是什么?文件中是否有一个重要的行?数字是否总是按顺序排列?文件将是可变的,但带有测试的行将始终存在;这是重要的一行。该行中的数字将始终以该格式存在。使用具有内置正则表达式支持的功能更强的shell将比使用外部工具进行提取更加高效。我的意思是,是的,这可以在纯POSIX sh中完成,但是在awk/sed/whatnot的启动时间内,您将受到性能的影响。
declare-a
在POSIX sh中不可用(因为那里通常不支持数组)…而且,
declare-a arr=$(…)
只为arr的第一个元素赋值;需要
declare-a arr=($(…)
才能分配给多个元素(使用字符串拆分和全局扩展从扩展中接收的单个字符串中获取这些元素,这是一种不理想的做法);或者,在bash4.x中,
readarray
mapfile
可以直接填充数组!我需要在sh中这样做,不幸的是,如果您要在这里使用
eval
,我建议(强烈!)只过滤数值,这样攻击者就不能将代码插入到您的存储库中,从而将
测试运行:$(rm-rf/)
——或者下载并运行外壳代码的东西——放入测试套件的输出中。能够进行权限升级,从将恶意代码检查到git repo,再到在live infrastructure中运行代码,这不是一件好事。你是对的,eval可能是邪恶的,我将用警告更新我的答案(从另一个角度讲,所有caps变量名都是不好的做法;请参阅的第四段,记住shell变量和环境变量共享一个名称空间)。很好。非常好。我需要开始更频繁地使用这种技术。
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
IFS=, read part1 part2 part3 part4 <<EOF
$(grep '^Tests run' input.txt)
EOF
run=${part1#*: }
failures=${part2#*: }
errors=${part3#*: }
skipped=${part4#*: }