从awk命令中设置当前shell变量

23

有没有办法通过 awk 在当前 shell 中设置一个变量?

我想在处理文件并打印一些数据时保存行数 - 在这种情况下,保存 FNR 的数量。

但是,我似乎找不到一种使用 FNR 值设置 shell 变量的方法;如果不能实现这个,我将不得不从输出文件中读取 FNR 来设置 num_lines 变量的值。

我已经尝试了一些组合,例如 awk 'END{system(...)}',但无法使其正常工作。是否有其他解决方案?

7个回答

41

这是另一种方法。

当您拥有变量的在一个单独的变量中,而您想将它们拆分时,这尤其有用。例如,您有一个数据库中单行的值列表,您想从中创建变量。

val="hello|beautiful|world" # assume this string comes from a database query
read a b c <<< $( echo ${val} | awk -F"|" '{print $1" "$2" "$3}' )

echo $a #hello
echo $b #beautiful
echo $c #world

在这种情况下,我们需要使用“here string”,即 <<<,因为read命令无法从管道中读取,而是从stdin中读取


2
那正是我所需要的!我已经有一个包含用空格分隔的字符串的变量,所以我只需将其作为输入提供(就像在“read a b c <<< $var”中一样)。谢谢。 - Michele Dall'Agata
有一件事需要提到,如果字段用空格分隔,可以简化为:read a b c <<< $(echo ${val}) - Nick X
相反,如果您的字段中有空格,则此方法无法正常工作。您将得到一个错误的输出分配到变量中的结果,其中“runoff”被读入最后一个变量中,包括空格。 - MeanderingCode
实际上,在字段中包含空格时,将其读入变量中是没有问题的。如果包含空格的字段由另一个分隔符分隔,则可以像这样设置IFS(以及awk分隔符)(这里逗号是分隔符):IFS=',' read aa bb <<< $(echo ${line} | awk -F',' '{print $3","$4}') 如果只有空格,则按其编号仅取所需的空格。 - nrp

31
$ echo "$var"

$ declare $( awk 'BEGIN{print "var=17"}' )
$ echo "$var"
17

这就是为什么你应该使用declare而不是eval的原因:

$ eval $( awk 'BEGIN{print "echo \"removing all of your files, ha ha ha....\""}' )
removing all of your files, ha ha ha....

$ declare $( awk 'BEGIN{print "echo \"removing all of your files\""}' )
bash: declare: `"removing': not a valid identifier
bash: declare: `files"': not a valid identifier

请注意在第一个例子中,eval会执行awk打印的任何字符串,这可能会意外地造成非常糟糕的后果!


谢谢你的建议,但只要是我自己编写的代码,使用eval并不会有太大问题。 - Rubens
12
@Rubens,直到你有一天不小心做了什么坏事。如果你现在养成编写安全代码的习惯,将来就不会受到伤害。 - glenn jackman
@glennjackman +1 感谢你的提示;我想这就是为什么安全编码在这里如此受重视的原因 (: - Rubens
2
我从经验中说出来的... :( - glenn jackman

7

你无法从子shell导出变量到父shell。不过你还有其他选择,包括:

  1. Make another pass of the file using AWK to count records, and use command substitution to capture the result. For example:

    FNR=$(awk 'END {print FNR}' filename)
    
  2. Print FNR in the subshell, and parse the output in your other process.
  3. If FNR is the same as number of lines, you can call wc -l < filename to get your count.

我想避免第二个和第三个选项,但我并没有从第一个选项中真正理解这个想法。第一个选项不是我在第三个选项中使用的相同技术吗? - Rubens
是的,FNR=$(awk 'END {print FNR}' filename)FNR=$(wc -l filename | awk '{print $1}') 相当相似,除了一个程序计算行数 -- awk/wc - Rubens
3
不过,你并不真的需要使用 wc+awk 的组合,你只需使用 wc -l <文件名> 就可以了。 - Ed Morton

4

警告:如果您尝试使用一些答案中建议的declare,请注意。

eval没有这个问题。

如果提供给declare的awk(或其他表达式)结果为空字符串,则declare将转储当前环境。这几乎肯定不是您想要的。

例如,如果您的awk模式在输入中不存在,则永远不会打印输出,因此您将得到意外的行为。

以下是一个例子...

 unset var
 var=99
 declare $( echo "foobar" | awk '/fail/ {print "var=17"}' )
 echo "var=$var"
var=99
The current environment as seen by declare is printed
and $var is not changed

对存储在awk变量中的值进行轻微更改,并在最后打印该值即可解决此问题....

 unset var
 var=99
 declare $( echo "foobar" | awk '/fail/ {tmp="17"} END {print "var="tmp}' )
 echo "var=$var"
var=
This time $var is unset ie: set to the null string var=''
and there is no unwanted output.

展示如何使用匹配模式实现此功能。
 unset var
 var=99
 declare $( echo "foobar" | awk '/foo/ {tmp="17"} END {print "var="tmp}' )
 echo "var=$var"
var=
This time $var is unset ie: set to the null string var=''
and there is no unwanted output.

问题不在于声明“默认行为”,而在于未使用引号传递$()。这样会留下空白,而没有声明有关参数的任何智能。正确的解决方案是,或者至少包含引用声明参数:declare "$( ... )" - Gerard van Helden

1
为了总结目前的内容,我将分享一下如何从使用awk读取单行文件的脚本中设置shell环境变量。很明显,可以使用/pattern/代替NR==1来找到所需的变量。请注意保留html标签。
# export a variable from a script (such as in a .dotfile)
declare $( awk 'NR==1 {tmp=$1} END {print "SHELL_VAR=" tmp}' /path/to/file )
export SHELL_VAR

如果没有参数发出declare命令,这将避免大量的变量输出,以及盲目eval的安全风险。


1

awk 输出赋值语句:

MYVAR=NewValue

然后在您的Shell脚本中,eval您的awk脚本的输出:

eval $(awk ....)
# then use $MYVAR

编辑:人们建议使用declare而不是eval,如果内部脚本打印除赋值以外的内容,这样可以稍微减少一些错误。它仅适用于bash,但当shell是bash且脚本具有#!/bin/bash时,正确说明此依赖关系就可以了。

eval $(...)变体被广泛使用,现有程序生成适用于eval但不适用于declare的输出(例如lesspipe);因此,理解它非常重要,而仅适用于bash的变体则“太局限”了。


这是唯一技术上可能的答案。 - anishsane
@anishsane 对我来说很合理(: 我没想到这个,谢谢Anton Kovalenko! - Rubens
我会使用 declare 而不是 eval - chepner
@chepner declare 是仅限于 Bash 的,这并不一定是坏事,但这就是为什么我默认不使用它的原因。 - Anton Kovalenko
较为合理的解决方案是 var=$(awk 'END { print 17 }' </dev/null),其中 Awk 只提供值,而 shell 决定如何处理它。 - tripleee
显示剩余6条评论

-1

echo "第一个参数: $1" for ((i=0 ; i < $1 ; i++)); do echo "内部" echo "欢迎您第 $i 次。" cat man.xml | awk '{ x[NR] = $0 } END { for ( i=2 ; i<=NR ; i++ ) { if (x[i] ~ // ) {x[i+1]=" '$i'"}print x[i] }} ' > $i.xml done echo "完成"


1
你能否规范你的代码并添加一些解释,以便其他人可以从中学习? - Nico Haase

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接