打印除前三列外的所有列


112

太麻烦了:

awk '{print " "$4" "$5" "$6" "$7" "$8" "$9" "$10" "$11" "$12" "$13}' things

43
有什么原因不能仅仅使用cut -f3-
卡斯卡贝尔

1
@hhh不错。.我喜欢总结答案的想法。
克里斯·西摩

2
@Jefromi-因为存在剪切中的行缓冲问题,而awk没有该问题:stackoverflow.com/questions/14360640/…–
sdaau,


@Jefromi- cut{}动作之前也没有正则表达式,然后它用字段定界符(可变数量的空格?)变得笨拙,您必须手动指定它们。我认为OP希望听到一些shift N不存在的命令。最接近的是$1="";$2="";(...);print},但是在我的情况下,它留下了一些前导空格(可能是分隔符)。
Tomasz Gandor

Answers:


50

不添加额外的前导或尾随空格的解决方案:

awk '{ for(i=4; i<NF; i++) printf "%s",$i OFS; if(NF) printf "%s",$NF; printf ORS}'

### Example ###
$ echo '1 2 3 4 5 6 7' |
  awk '{for(i=4;i<NF;i++)printf"%s",$i OFS;if(NF)printf"%s",$NF;printf ORS}' |
  tr ' ' '-'
4-5-6-7

Sudo_O使用三元运算符提出了一种优雅的改进NF?ORS:OFS

$ echo '1 2 3 4 5 6 7' |
  awk '{ for(i=4; i<=NF; i++) printf "%s",$i (i==NF?ORS:OFS) }' |
  tr ' ' '-'
4-5-6-7

EdMorton提供了一种保留字段之间原始空白的解决方案:

$ echo '1   2 3 4   5    6 7' |
  awk '{ sub(/([^ ]+ +){3}/,"") }1' |
  tr ' ' '-'
4---5----6-7

BinaryZebra还提供了两个很棒的解决方案:(
这些解决方案甚至还保留了原始字符串的尾随空格)

$ echo -e ' 1   2\t \t3     4   5   6 7 \t 8\t ' |
  awk -v n=3 '{ for ( i=1; i<=n; i++) { sub("^["FS"]*[^"FS"]+["FS"]+","",$0);} } 1 ' |
  sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'
"4...5...6.7.->.8->."

$ echo -e ' 1   2\t \t3     4   5   6 7 \t 8\t ' |
  awk -v n=3 '{ print gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1); }' |
  sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'
"4...5...6.7.->.8->."

larsr在评论中给出的解决方案几乎是正确的:

$ echo '1 2 3 4 5 6 7' | 
  awk '{for (i=3;i<=NF;i++) $(i-2)=$i; NF=NF-2; print $0}' | tr  ' ' '-'
3-4-5-6-7

这是larsr解决方案的固定和参数化版本:

$ echo '1 2 3 4 5 6 7' | 
  awk '{for(i=n;i<=NF;i++)$(i-(n-1))=$i;NF=NF-(n-1);print $0}' n=4 | tr ' ' '-'
4-5-6-7

2013年9月之前的所有其他答案都不错,但要添加额外的空格:


EdMorton的答案对我不起作用(bash 4.1.2(1)版本,GNU Awk 3.1.7或bash 3.2.25(1)版本,GNU Awk 3.1.5),但在这里找到另一种方法:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch

1
@elysch不,通常不会起作用,只是在给定某些特定输入值的情况下才起作用。请参阅我在答案下方的评论下方添加的评论。
Ed Morton

1
嗨@fedorqui 我的答案是第一个。在我最初的答案中,我正在解释为什么其他答案不正确(多余的前导空格或结尾空格)。有些人在评论中提出了增强功能。我们已要求OP选择一个更正确的答案,而他/她已经选择了我的答案。在其他一些贡献者编辑完我的答案以引用那里的答案之后(请参阅历史记录)。你清楚吗?您对我有什么建议,以提高答案的可理解性?干杯;-)
olibre 2016年

1
您绝对正确,对于我的误会,我深感抱歉。我快速阅读了答案,但没有注意到您的原始答案(是的,我读得太快了)。使用一个不错的技巧为答案本身+1,以循环至NF-1,然后打印最后一个元素,以避免多余的空格。再次抱歉!(将在一天左右的时间内删除我的评论,以免引起未来读者的误解)。
fedorqui'SO stop harm''

1
我将使用某种标题:<您的答案>,然后是水平规则,后跟一个大标题“比较其他答案”。否则,将此比较移至另一个答案,因为显然人们倾向于在“给我代码”的愿景中使用简短答案
:)

75
awk '{for(i=1;i<4;i++) $i="";print}' file

4
如果OFS您不处理NF记录中的前导空间,这将留下前导。
克里斯·西摩

70

使用切割

$ cut -f4-13 file

或者如果您坚持要求awk并且$ 13是最后一个字段

$ awk '{$1=$2=$3="";print}' file

其他

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

14
在上一个示例中,使用“ NF”可能比使用“ 13”更好。
glenn jackman 2010年

2
由OP决定的2种情况。如果13是最后一个字段,则可以使用NF。如果不是,则使用13是适当的。
ghostdog74

3
2nd需要从$ 0开始删除3份OFS。第三会更好printf "%s ",$i,因为您不知道是否$i可能包含%s或类似内容。但这将在末尾打印出额外的空间。
dubiousjim 2012年

38

试试这个:

awk '{ $1=""; $2=""; $3=""; print $0 }'

1
因为它是动态的,所以很好。您可以在末尾添加列,而不必重写脚本。
MinceMan 2012年

1
这表明问题试图解决的确切问题恰恰相反。从第100个字段打印该怎么办?请注意,您不会与NF您打交道,所以您离开领导OFS
克里斯·西摩

24

正确的方法是使用RE间隔,因为它可以让您简单地声明要跳过多少个字段,并为其余字段保留字段间的间隔。

例如,在给定输入格式的情况下,跳过前3个字段而不影响其余字段之间的间隔,我们似乎在这个问题中讨论的只是:

$ echo '1   2 3 4   5    6' |
awk '{sub(/([^ ]+ +){3}/,"")}1'
4   5    6

如果要容纳前导空格和非空格空格,但又要使用默认FS,则它是:

$ echo '  1   2 3 4   5    6' |
awk '{sub(/[[:space:]]*([^[:space:]]+[[:space:]]+){3}/,"")}1'
4   5    6

如果您使用的是FS,而您不能在字符集中取反,则可以先将其转换为单个字符(如果是单个字符,则RS是理想的,因为RS不能出现在字段中,否则请考虑使用SUBSEP),然后应用RE间隔替换,然后转换为OFS。例如,如果“。”链分隔字段:

$ echo '1...2.3.4...5....6' |
awk -F'[.]+' '{gsub(FS,RS);sub("([^"RS"]+["RS"]+){3}","");gsub(RS,OFS)}1'
4 5 6

显然,如果OFS是单个字符并且它不能出现在输入字段中,则可以将其减少为:

$ echo '1...2.3.4...5....6' |
awk -F'[.]+' '{gsub(FS,OFS); sub("([^"OFS"]+["OFS"]+){3}","")}1'
4 5 6

然后,您将遇到与所有重新分配字段的基于循环的解决方案相同的问题-FS转换为OFS。如果这是一个问题,则需要研究GNU awks的patsplit()函数。


不适用于我(bash 4.1.2(1)版本,GNU Awk 3.1.7或bash 3.2.25(1)版本,GNU Awk 3.1.5),但在这里找到另一种方式:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch

2
不,如果$ 1或$ 2包含$ 3设置为的字符串,那将失败。例如echo ' That is a test' | awk '{print substr($0, index($0,$3))}',尝试一下,您会发现a$ 3与$ 1 的a内部匹配That。在类似gawk的旧版本中,您需要使用flag启用RE interval --re-interval
Ed Morton 2014年

2
您是对的,没有注意到。顺便说一句,非常感谢您的评论。很多时候想使用带有“ {}”的正则表达式来指定元素数量,却从未在男人中看到“ --re-interval”。为您+1。
elysch 2014年

1
1是一个真实条件,因此调用了打印当前记录的默认awk操作。
Ed Morton 2014年

1
idk这是多么规范,但是我现在添加了答案。
Ed Morton

10

当前,几乎所有答案都添加前导空格,尾随空格或其他分隔符。要从第四个字段中选择,其中分隔符为空白,而输出分隔符为单个空格,则使用awk

awk '{for(i=4;i<=NF;i++)printf "%s",$i (i==NF?ORS:OFS)}' file

要设置起始字段的参数,您可以执行以下操作:

awk '{for(i=n;i<=NF;i++)printf "%s",$i (i==NF?ORS:OFS)}' n=4 file

还有结尾字段:

awk '{for(i=n;i<=m=(m>NF?NF:m);i++)printf "%s",$i (i==m?ORS:OFS)}' n=4 m=10 file

6
awk '{$1=$2=$3="";$0=$0;$1=$1}1'

输入项

1 2 3 4 5 6 7

输出量

4 5 6 7

4
echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) print $i }'

3
或者使它们位于同一行,将$ 3分配给$ 1,依此类推,然后将NF更改为正确的字段数。echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) $(i-2)=$i; NF=NF-2; print $0 }'
larsr 2012年

嗨@larsr 您建议的命令行是唯一的正确答案。所有其他答案都添加了额外的空格(前导或尾随)。请在新答案中发布您的命令行,我将对其进行投票;-)
olibre 2013年

1
@sudo_O,您好,我正在与@larsr谈谈他在评论中建议的命令行。我花了大约五分钟的时间才弄清楚quiproco(误解)。我同意,@ Vetsin答案ORS在字段之间插入新行()。勇敢地争取你的主动权(我喜欢你的回答)。干杯
olibre 2013年

3

避免使用print语句的另一种方法:

 $ awk '{$1=$2=$3=""}sub("^"FS"*","")' file

在awk中,当条件为true时,print是默认操作。


@lhf 答案有所有问题..它更短。
克里斯·西摩

很好的主意;)比我的答案还好!(去年我已经对您的回答进行了投票)干杯
olibre 2014年

应该是:awk '{$1=$2=$3=""}sub("^"OFS"+","")' file与OFS一样,更改$ 1,$ 2和$ 3内容后剩下的内容。

3

我不敢相信没有人提供简单的外壳:

while read -r a b c d; do echo "$d"; done < file

对于类似的解决方案,则为+1 ...但是,如果file大小较大(> 10-30KiB),则可能会出现性能问题。对于大文件,该awk解决方案性能更好。
TrueY 2014年

3

选项1到3存在多个空格问题(但很简单)。这就是开发选项4和5的原因,该选项可以毫无问题地处理多个空白。当然,如果同时使用选项4或5,n=0则会保留任何前导空格,因为n=0意味着不会拆分。

选项1

一个简单的剪切解决方案(使用单个定界符):

$ echo '1 2 3 4 5 6 7 8' | cut -d' ' -f4-
4 5 6 7 8

选项2

强制执行awk重新计算有时可以解决添加的前导空格的问题(适用于某些版本的awk):

$ echo '1 2 3 4 5 6 7 8' | awk '{ $1=$2=$3="";$0=$0;} NF=NF'
4 5 6 7 8

选项3

打印每个格式为的字段printf将提供更多控制权:

$ echo '    1    2  3     4   5   6 7     8  ' |
  awk -v n=3 '{ for (i=n+1; i<=NF; i++){printf("%s%s",$i,i==NF?RS:OFS);} }'
4 5 6 7 8

但是,所有先前的答案将字段之间的所有FS更改为OFS。让我们为此建立一些解决方案。

选项4

带有sub的循环可以删除字段和定界符,并且更容易移植,并且不会触发将FS更改为OFS:

$ echo '    1    2  3     4   5   6 7     8  ' |
awk -v n=3 '{ for(i=1;i<=n;i++) { sub("^["FS"]*[^"FS"]+["FS"]+","",$0);} } 1 '
4   5   6 7     8

注意: “ ^ [” FS“] *”接受带有前导空格的输入。

选项5

很有可能构建一个不添加额外的前导或尾随空格,并使用gensubGNU awk中的函数保留现有空格的解决方案,如下所示:

$ echo '    1    2  3     4   5   6 7     8  ' |
awk -v n=3 '{ print gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1); }'
4   5   6 7     8 

给定一个count,它也可以用来交换一个字段列表n

$ echo '    1    2  3     4   5   6 7     8  ' |
  awk -v n=3 '{ a=gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1);
                b=gensub("^(.*)("a")","\\1",1);
                print "|"a"|","!"b"!";
               }'
|4   5   6 7     8  | !    1    2  3     !

当然,在这种情况下,OFS用于分隔行的两个部分,并且仍打印字段的尾随空白。

注意1: ["FS"]*用于在输入行中保留前导空格。


嗨,BZ您的回答很好。但是,选项3不适用于以空格开头的字符串(例如" 1 2 3 4 5 6 7 8 ")。选项4不错,但是使用以空格开头的字符串保留前导空格。您认为这是否可以解决?您可以使用命令echo " 1 2 3 4 5 6 7 8 " | your awk script | sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'来验证前导/中间/尾部的空格...干杯;)
olibre

嗨@olibre 选项3出现空白而失败的原因是开发选项4和5的原因。选项4仅在输入具有前导空格 n设置为0(n = 0)时才保留前导空格。如果没有选择任何字段(无法解决IMO),我相信这是正确的答案。干杯。

行。感谢您提供其他信息:-)请提供以下额外信息来改善您的回答:-)干杯
olibre

完美:-)可惜的是您的用户被禁用了:-(
olibre

1

Cut具有--complement标志,可轻松(快速)删除列。产生的语法与您要执行的操作类似-使解决方案更易于阅读/理解。当您要删除不连续的列时,补码也适用。

$ foo='1 2 3 %s 5 6 7'
$ echo "$foo" | cut --complement -d' ' -f1-3
%s 5 6 7
$

您能再解释一下您的答案吗?
祖鲁语2015年

上面的编辑有助于理解吗?重点是使用cut的补码标志。与基于AWK或基于Perl的解决方案相比,该解决方案应该是一种更快,更简洁的实现。同样,可以切割任意列。
迈克尔·

1

不添加前导或尾随空格的Perl解决方案:

perl -lane 'splice @F,0,3; print join " ",@F' file

perl自动@F拆分数组从索引处开始,0而awk字段以$1


Perl解决方案,用逗号分隔数据:

perl -F, -lane 'splice @F,0,3; print join ",",@F' file

Python解决方案:

python -c "import sys;[sys.stdout.write(' '.join(line.split()[3:]) + '\n') for line in sys.stdin]" < file


0

对我来说,最紧凑,最合规的解决方案是

$ a='1   2\t \t3     4   5   6 7 \t 8\t '; 
$ echo -e "$a" | awk -v n=3 '{while (i<n) {i++; sub($1 FS"*", "")}; print $0}'

如果您还有更多行要处理,例如foo.txt文件,请不要忘记将i重置为0:

$ awk -v n=3 '{i=0; while (i<n) {i++; sub($1 FS"*", "")}; print $0}' foo.txt

感谢您的论坛。


0

当我对第一个高度赞扬但错误的答案感到恼火时,我发现足以在此处写一个答复,这里错误的答案被标记为这样,这是我的观点。我不喜欢提议的解决方案,因为我看不出有什么理由使答案如此复杂。

我有一个日志,其中带有IP地址的$ 5之后可以是更多文本,也可以是没有文本。我需要从IP地址到行尾的所有内容,$ 5之后应该有什么。就我而言,这实际上是一个awk程序,而不是一个awk oneliner,因此awk必须解决问题。当我尝试使用旧的漂亮外观和最被推崇但完全错误的答案删除前4个字段时:

echo "  7 27.10.16. Thu 11:57:18 37.244.182.218 one two three" | awk '{$1=$2=$3=$4=""; printf "[%s]\n", $0}'

它吐出错误且无用的响应(我添加了[]进行演示):

[    37.244.182.218 one two three]

相反,如果列是固定宽度的,直到需要切点和awk,则正确而简单的答案是:

echo "  7 27.10.16. Thu 11:57:18 37.244.182.218 one two three" | awk '{printf "[%s]\n", substr($0,28)}'

产生所需的输出:

[37.244.182.218 one two three]

0

我发现了另一种可能性,也许它也可能有用...

awk 'BEGIN {OFS=ORS="\t" }; {for(i=1; i<14; i++) print $i " "; print $NF "\n" }' your_file

注意: 1.对于表格数据,从$ 1到$ 14列


0

使用方式:

cut -d <The character between characters> -f <number of first column>,<number of last column> <file name>

例如:如果您file1包含:car.is.nice.equal.bmw

运行:cut -d . -f1,3 file1 将打印car.is.nice


似乎您的解决方案可能是落后的。请查看问题标题打印所有*但*前三列
Stefan Crain

-1

这与先前的答案并不遥远,但确实解决了两个问题:

cols.sh

#!/bin/bash
awk -v s=$1 '{for(i=s; i<=NF;i++) printf "%-5s", $i; print "" }'

现在,您可以使用将作为起始列的参数进行调用:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 3 
3    4    5    6    7    8    9    10   11   12   13   14

要么:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 7 
7    8    9    10   11   12   13   14

这是1索引的;如果您更喜欢零索引,请i=s + 1改用。

此外,如果您需要为起始索引结束索引提供参数,请将文件更改为:

#!/bin/bash
awk -v s=$1 -v e=$2 '{for(i=s; i<=e;i++) printf "%-5s", $i; print "" }'

例如:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 7 9 
7    8    9

所述%-5s对齐到的结果作为5个字符的范围内的列; 如果这还不够,请增加数量,或者%s如果您不在乎对齐,请改为使用(带空格)。


-1

基于AWK printf的解决方案可避免%问题,并且独特之处在于,如果要打印的列少于4列,则不返回任何内容(不返回字符):

awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'

测试:

$ x='1 2 3 %s 4 5 6'
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
%s 4 5 6
$ x='1 2 3'
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
$ x='1 2 3 '
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
$
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.