在awk中打印其余字段

54

假设我们有这个数据文件。

john 32 maketing executive
jack 41 chief technical officer
jim  27 developer
dela 33 assistant risk management officer

我想使用 awk 命令打印输出

john maketing executive
jack chief technical officer
jim  developer
dela assistant risk management officer

我知道可以使用for来完成它。

awk '{printf $1;  for(i=3;i<=NF;i++){printf " %s", $i} printf "\n"}' < file

问题在于它太长了,看起来很复杂。

是否有其他简短的方法来打印其余字段


一个简单的技巧是将$2设置为空字符串,然后打印$0(所有字段)--尽管这会为空字段提供一个额外的分隔符。 - mkfs
2
三年后,你帮了我。但是如果不将“<NF”更改为“<=NF”,你将跳过最后一个字段;) - Koreth
2
三年后,我编辑了这个问题,将<NF更改为<=NF,以修复@Koreth指出的错误。 - Katie Kilian
7个回答

72

6
不清除额外的空格,使用不必要的 print $0,可以用简单的 1 替换。 - Jotne
@Jotne 当我使用1代替print $0时,awk没有输出任何内容。你确定它们是等价的吗? - Alex
1
@Alex移除print $0并在闭合}后加上1 - blakeoft

9

使用默认FS时,可靠地使用GNU awk进行gensub()操作:

$ gawk -v delNr=2 '{$0=gensub("^([[:space:]]*([^[:space:]]+[[:space:]]+){"delNr-1"})[^[:space:]]+[[:space:]]*","\\1","")}1' file
john maketing executive
jack chief technical officer
jim  developer
dela assistant risk management officer

使用其他 awk,您需要使用 match() 和 substr() 而不是 gensub()。请注意,上面的变量 delNr 告诉awk您要删除哪个字段:

$ gawk -v delNr=3 '{$0=gensub("^([[:space:]]*([^[:space:]]+[[:space:]]+){"delNr-1"})[^[:space:]]+[[:space:]]*","\\1","")}1' file
john 32 executive
jack 41 technical officer
jim  27
dela 33 risk management officer

不要这样做:

awk '{sub($2 OFS, "")}1'

由于$2中的相同文本可能位于$1的末尾,而/或者$2可能包含RE元字符,因此您很有可能以这种方式删除错误的字符串。

不要这样做:

awk '{$2=""}1' file

由于添加了FS,它将把字段之间的所有连续空格压缩成一个单独的空格字符。

不要这样做:

awk '{$2="";sub("  "," ")}1' file

由于上述的空间压缩问题,并且它依赖于单个空格的硬编码FS(默认值,因此可能不太糟糕),更重要的是,如果$1之前有空格,则会删除其中一个空格而非添加在$1和$2之间。
最后值得一提的是,在新版本的gawk中有一个名为patsplit()的新功能,它的作用类似于split(),但除了创建字段数组外,它还创建了字段之间的空间数组。这意味着您可以在数组中操作字段和字段之间的空格,因此,如果您操作字段,则不必担心awk使用OFS重新编译记录。然后,只需从数组中打印所需的字段即可。有关更多信息,请参见http://www.gnu.org/software/gawk/manual/gawk.html#String-Functions中的patsplit()。

2
看到这些问题,人们不禁怀疑awk是否真的是完成此工作的最佳工具。例如,如果字段由管道或逗号分隔,则整个awk代码需要重新编写。 - anubhava
根据您的输入而定。如果字段之间是单个字符,则使用cut更好。如果有其他内容,则gawk+gensub()或sed(语法非常相似)可能是最佳选择。这两个选项在试图描述多字符正则表达式的否定时都可能遇到问题,因此您需要查看gawk+patsplit()或gawk+FPAT。不幸的是,没有万能解决方案。 - Ed Morton
很棒的答案,我希望我能给你+2分。一个问题是,这段代码比“for”循环解决方案要长得多。f - Shiplu Mokaddim
@shiplu.mokadd.im - 正确的,但它保留了原始的空格,而你发布的for循环将不会产生你指定的输出。顺便说一句,关于你发的那个for循环-永远不要使用printf打印输入数据,例如 printf $1,因为如果你的输入数据包含printf格式控制字符(如%),这将失败而不能输出。相反,总是使用printf "%s",$1来打印输入数据。此外,要打印换行符只需使用print ""即可,无需使用printf "\n" - Ed Morton

6
您可以使用简单的awk命令,如下所示:
awk '{$2=""}1' file

然而,这将在您的输出中产生额外的OFS,可以通过此awk避免。
awk '{sub($2 OFS, "")}1' file

或者使用此 tr 和 cut 组合:

在 Linux 上:

tr -s ' ' < file | cut -d ' ' -f1,f3-

在OSX上:

tr -s ' ' < file | cut -d ' ' -f1 -f3-

你的错误还在,应该是 -f1,3- 而不是 -f1,f3- - Adrian Frühwirth
1
不应该使用 awk '{sub($2 OFS, "")}1',因为在 $2 中的相同文本可能出现在 $1 的末尾,或者 $2 可能包含 RE 元字符,因此以这种方式删除字符串会很有可能错误。 - Ed Morton
@EdMorton:关于正则表达式的观点是正确的。我不知道如果在sub中没有使用/str/形式,它会被视为字面文本而不是正则表达式。 - anubhava
2
@anubhava - 不,唯一一个在另一个字符串中查找字符串而不是正则表达式的awk函数是index()。 - Ed Morton
1
@anubhava - 正确,没有简单的方法,但请查看我的答案以获得一个强大的方法。 - Ed Morton
显示剩余7条评论

4
这将删除字段#2并清除多余的空格。
awk '{$2="";sub("  "," ")}1' file

这里的额外的 1 是干什么用的? - Shiplu Mokaddim
2
@shiplu.mokadd.im 1 评估为真,触发默认块 ({ print $0 })。 - Adrian Frühwirth
不会清除任何内容,但与所有现有字段的重写一样 - 它将 IFS(一个或多个连续)替换为单个 OFS。例如,这是实现“规范化空格”过滤器的一种方法:awk '{$1=$1}1' - Krzysztof Jabłoński

3
另一种方法是只使用sed来替换第一个数字和空格的匹配: sed 's|[0-9]\+\s\+||' file

0

使用 awk 方法,不需要 gawk 或任何状态变化:

awk '{print $1 " " substr($0, index($0, $3));}' datafile

更新

这是一个稍长一些的解决方案,但可以处理当$1或$2包含$3的情况:

awk '{print $1 " " substr($0, length($1 $2) + 1);}' data

如果您有自定义字段分隔符,甚至可以更加强大:

awk '{print $1 " " substr($0, length($1 FS $2 FS) + 1);}' data

-1

不要改变 $n。如果您在某个部分有更多的空格要保留,它将被减少为一个。


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