使用 awk 打印从第 n 到最后的所有列

Posted

技术标签:

【中文标题】使用 awk 打印从第 n 到最后的所有列【英文标题】:Using awk to print all columns from the nth to the last 【发布时间】:2011-02-27 01:05:46 【问题描述】:

这条线一直有效,直到我在第二个字段中有空格。

svn status | grep '\!' | gawk 'print $2;' > removedProjs

有没有办法让 awk 以 2 美元或更高的价格打印所有内容? ($3, $4.. 直到我们不再有列?)

我想我应该补充一点,我是在带有 Cygwin 的 Windows 环境中执行此操作的。

【问题讨论】:

顺便说一句,grep | awk is an antipattern -- 你想要awk '/!/ print $2 ' Unix“剪切”更容易...svn status | grep '\!' | cut -d' ' -f2- > removedProjs print rest of the fields in awk的可能重复 @tripleee:我很高兴你提到了这一点 - 我很沮丧地看到它无处不在! 【参考方案1】:

投票最多的answer by zed_0xff 对我不起作用。

我有一个日志,其中 5 美元之后的 IP 地址可以是更多文本或没有文本。如果 5 美元之后有任何内容,我需要从 IP 地址到行尾的所有内容。就我而言,这实际上是在 awk 程序中,而不是 awk 单行程序,因此 awk 必须解决问题。当我尝试使用 zed_0xff 提出的解决方案删除前 4 个字段时:

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

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

[    37.244.182.218 one two three]

甚至有一些建议将 substr 与这个错误的答案结合起来,但这只会使事情复杂化。它没有任何改进。

相反,如果在切割点和需要 awk 之前列的宽度是固定的,则正确答案是:

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

产生所需的输出:

[37.244.182.218 one two three]

【讨论】:

【参考方案2】:

我想将建议的答案扩展到字段可能由 几个空格 分隔的情况——我想这就是 OP 不使用 cut 的原因。

我知道 OP 询问了 awk,但 sed 方法可以在这里工作(例如打印从第 5 列到最后的列):

纯 sed 方法

  sed -r 's/^\s*(\S+\s+)4//' somefile

解释:

s/// 是执行替换的标准命令 ^\s* 匹配行首的任何连续空格 \S+\s+ 表示一列数据(非空白字符后跟空白字符) ()4 表示模式重复 4 次。

sed 和剪切

  sed -r 's/^\s+//; s/\s+/\t/g' somefile | cut -f5-

只需用一个制表符替换连续的空格;

tr 和剪切: tr 也可以通过-s 选项用于squeeze consecutive 字符。

  tr -s [:blank:] <somefile | cut -d' ' -f5-

【讨论】:

我同意 sed 最适合这个问题。注意:您提供的cut 示例不会在您尝试提取的部分中保留连续的空格。考虑这个输入:a b c d The rest。如果您只保留纯 sed 方法,您的答案会更好。也可以使用-E 而不是-r 以实现可移植性。此外,由于 \s 是 GNU 扩展,请将 \s 替换为 [ \t] 并将 \S 替换为 [^ \t]【参考方案3】:

打印所有列:

awk 'print $0' somefile

打印除第一列以外的所有内容:

awk '$1=""; print $0' somefile

打印除前两列之外的所有列:

awk '$1=$2=""; print $0' somefile

【讨论】:

陷阱:留下一个前导空格悬空:( @raphinesse 你可以用awk '$1=""; print substr($0,2)' input_filename &gt; output_filename修复它 这不适用于非空白分隔符,用空格替换它们。 对于非空白分隔符,您可以指定输出字段分隔符 (OFS),例如到逗号:awk -F, -vOFS=, '$1=""; print $0' 你最终会得到一个初始分隔符($1 仍然包括在内,就像一个空字符串一样)。你可以用sed 去掉它:awk -F, -vOFS=, '$1=""; print $0' | sed 's/^,//' AWK 就像是实现三个愿望的过于文字的精灵【参考方案4】:
awk ' for(i=3; i<=NF; ++i) printf $i""FS; print "" '

lauhub提出了这个正确、简单、快速的解决方案here

【讨论】:

【参考方案5】:

awk 函数返回$0 的子字符串,其中包括从beginend 的字段:

function fields(begin, end,    b, e, p, i) 
    b = 0; e = 0; p = 0;
    for (i = 1; i <= NF; ++i) 
        if (begin == i)  b = p; 
        p += length($i);
        e = p;
        if (end == i)  break; 
        p += length(FS);
    
    return substr($0, b + 1, e - b);

从字段 3 开始获取所有内容:

tail = fields(3);

要获取覆盖字段 3 到 5 的 $0 部分:

middle = fields(3, 5);

函数参数列表中的b, e, p, i废话只是awk声明局部变量的方式。

【讨论】:

【参考方案6】:

Perl:

@m=`ls -ltr dir | grep ^d | awk 'print \$6,\$7,\$8,\$9'`;
foreach $i (@m)

        print "$i\n";


【讨论】:

这没有回答问题,它概括了从第 N 列打印到末尾的要求。【参考方案7】:

如果您不想重新格式化您不切断的那部分行,我能想到的最佳解决方案写在我的答案中:

How to print all the columns after a particular number using awk?

它将给定字段编号 N 之前的内容切掉,并打印该行的所有其余部分,包括字段编号 N 并保持原始间距(它不会重新格式化)。字段的字符串是否也出现在该行的其他位置并不重要。

定义一个函数:

fromField ()  
awk -v m="\x01" -v N="$1" '$N=m$N; print substr($0,index($0,m)+1)'

并像这样使用它:

$ echo "  bat   bi       iru   lau bost   " | fromField 3
iru   lau bost   
$ echo "  bat   bi       iru   lau bost   " | fromField 2
bi       iru   lau bost 

输出维护所有内容,包括尾随空格

在你的特殊情况下:

svn status | grep '\!' | fromField 2 > removedProjs

如果您的文件/流在行中间不包含换行符(您可以使用不同的记录分隔符),您可以使用:

awk -v m="\x0a" -v N="3" '$N=m$N ;print substr($0, index($0,m)+1)'

第一种情况只会在包含罕见的十六进制字符数 1 的文件/流中失败

【讨论】:

【参考方案8】:

如果您使用 Bash 并且您可以使用尽可能多的 'x ' 作为您希望丢弃的元素并且如果它们没有被转义,它会忽略多个空格。

while read x b; do echo "$b"; done < filename

【讨论】:

【参考方案9】:

如果您想要格式化文本,请使用 echo 链接您的命令并使用 $0 打印最后一个字段。

例子:

for i in 8..11; do
   s1="$i"
   s2="str$i"
   s3="str with spaces $i"
   echo -n "$s1 $s2" | awk 'printf "|%3d|%6s",$1,$2'
   echo -en "$s3" | awk 'printf "|%-19s|\n", $0'
done

打印:

|  8|  str8|str with spaces 8  |
|  9|  str9|str with spaces 9  |
| 10| str10|str with spaces 10 |
| 11| str11|str with spaces 11 |

【讨论】:

【参考方案10】:
ls -la | awk 'o=$1" "$3; for (i=5; i<=NF; i++) o=o" "$i; print o '

来自this answer 还不错,但自然间距消失了。 然后请与此比较:

ls -la | cut -d\  -f4-

然后你会看到不同之处。

即使基于the answer 的ls -la | awk '$1=$2=""; print' 迄今为止被评为最佳,也不会保留格式。

因此我将使用以下内容,并且它还允许在开头显式选择列:

ls -la | cut -d\  -f1,4-

请注意,每个空格也算作列,因此例如在下面,第 1 列和第 3 列为空,第 2 列为 INFO,第 4 列为:

$ echo " INFO  2014-10-11 10:16:19  main " | cut -d\  -f1,3

$ echo " INFO  2014-10-11 10:16:19  main " | cut -d\  -f2,4
INFO 2014-10-11
$

【讨论】:

【参考方案11】:

这让我非常恼火,我坐下来写了一个类似cut 的字段规范解析器,用 GNU Awk 3.1.7 进行了测试。

首先,创建一个名为 pfcut 的新 Awk 库脚本,例如

sudo nano /usr/share/awk/pfcut

然后,粘贴下面的脚本并保存。之后的用法是这样的:

$ echo "t1 t2 t3 t4 t5 t6 t7" | awk -f pfcut --source '/^/  pfcut("-4"); '
t1 t2 t3 t4

$ echo "t1 t2 t3 t4 t5 t6 t7" | awk -f pfcut --source '/^/  pfcut("2-"); '
t2 t3 t4 t5 t6 t7

$ echo "t1 t2 t3 t4 t5 t6 t7" | awk -f pfcut --source '/^/  pfcut("-2,4,6-"); '
t1 t2 t4 t6 t7

为了避免输入所有内容,我想最好的方法是(另见Automatically load a user function at startup with awk? - Unix & Linux Stack Exchange)为~/.bashrc 添加一个别名;例如与:

$ echo "alias awk-pfcut='awk -f pfcut --source'" >> ~/.bashrc
$ source ~/.bashrc     # refresh bash aliases

...然后你可以打电话:

$ echo "t1 t2 t3 t4 t5 t6 t7" | awk-pfcut '/^/  pfcut("-2,4,6-"); '
t1 t2 t4 t6 t7

这里是pfcut脚本的来源:

# pfcut - print fields like cut
#
# sdaau, GNU GPL
# Nov, 2013

function spfcut(formatstring)

  # parse format string
  numsplitscomma = split(formatstring, fsa, ",");
  numspecparts = 0;
  split("", parts); # clear/initialize array (for e.g. `tail` piping into `awk`)
  for(i=1;i<=numsplitscomma;i++) 
    commapart=fsa[i];
    numsplitsminus = split(fsa[i], cpa, "-");
    # assume here a range is always just two parts: "a-b"
    # also assume user has already sorted the ranges
    #print numsplitsminus, cpa[1], cpa[2]; # debug
    if(numsplitsminus==2) 
     if ((cpa[1]) == "") cpa[1] = 1;
     if ((cpa[2]) == "") cpa[2] = NF;
     for(j=cpa[1];j<=cpa[2];j++) 
       parts[numspecparts++] = j;
     
     else parts[numspecparts++] = commapart;
  
  n=asort(parts); outs="";
  for(i=1;i<=n;i++) 
    outs = outs sprintf("%s%s", $parts[i], (i==n)?"":OFS); 
    #print(i, parts[i]); # debug
  
  return outs;


function pfcut(formatstring) 
  print spfcut(formatstring);

【讨论】:

好像你想使用cut,而不是awk @roblogic : unix cut 非常适合像几兆这样的小任务。也许低数百 MB 可能是切入点对于卷来说确实太慢了,而 awk 真正闪耀的地方。【参考方案12】:

我对这里提供的任何awk 解决方案都不满意,因为我想提取前几列然后打印其余的列,所以我转而使用perl。以下代码提取前两列,并按原样显示其余列:

echo -e "a  b  c  d\te\t\tf g" | \
  perl -ne 'my @f = split /\s+/, $_, 3; printf "first: %s second: %s rest: %s", @f;'

与Chris Koknat 的perl 解决方案相比,优势在于实际上只有前n 个元素从输入字符串中分离出来;字符串的其余部分根本没有分开,因此保持完整。我的示例通过混合使用空格和制表符来演示这一点。

要更改应提取的列数,请将示例中的 3 替换为 n+1。

【讨论】:

【参考方案13】:

这里的 awk 示例看起来很复杂,这里是简单的 Bash shell 语法:

command | while read -a cols; do echo $cols[@]:1; done

1 是您的第 n列,从 0 开始计数。


示例

鉴于此文件内容 (in.txt):

c1
c1 c2
c1 c2 c3
c1 c2 c3 c4
c1 c2 c3 c4 c5

这是输出:

$ while read -a cols; do echo $cols[@]:1; done < in.txt 

c2
c2 c3
c2 c3 c4
c2 c3 c4 c5

【讨论】:

【参考方案14】:

大多数带有 awk 的解决方案都会留下空格。这里的选项避免了这个问题。

选项 1

一个简单的剪切解决方案(仅适用于单个分隔符):

command | cut -d' ' -f3-

选项 2

强制 awk 重新计算有时会通过删除第一个字段来删除添加的前导空格 (OFS)(适用于某些版本的 awk):

command | awk ' $1=$2="";$0=$0; NF=NF'

选项 3

打印使用printf 格式化的每个字段将提供更多控制:

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

但是,所有先前的答案都将字段之间的所有重复 FS 更改为 OFS。让我们构建几个不这样做的选项。

选项 4(推荐)

带有 sub 的循环用于删除前面的字段和分隔符。 并使用 FS 的值而不是空间(可以更改)。 更便携,并且不会触发将 FS 更改为 OFS: 注意:^[FS]* 接受带有前导空格的输入。

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

选项 5

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

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

它也可以用来交换一组给定计数n的字段:

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

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

注意:[FS]* 用于在输入行中允许前导空格。

【讨论】:

【参考方案15】:

有一个重复的问题,simpler answer 使用 cut:

 svn status |  grep '\!' | cut -d\  -f2-

-d指定分隔符(空格)-f指定列列表(都从第2个开始)

【讨论】:

也可以使用“-b”指定位置(从第N个字符开始)。 请注意,虽然它执行与awk 版本相同的任务,但cut 存在行缓冲问题,awk 没有:***.com/questions/14360640/… 很好很简单,但有一个警告:awk 处理多个相邻的空格字符。作为 single 分隔符,而 cut 没有;另外——尽管这在手头的情况下不是问题——cut 只接受一个单一的文字字符。作为分隔符,而 awk 允许使用正则表达式。 基于此:***.com/a/39217130/8852408,很可能这个解决方案效率不高。 @Joaquin 我赞成您的评论,但随后在 120MB 的日志文件上运行了一些快速、非科学的基准测试:(time cut -d\ -f2- logfile.txt &gt; /dev/nulltime awk '$1=""; print $0' logfile.txt &gt; /dev/null)。 cut 命令(没有任何 grep)始终比 awk 等效命令快(cut 的平均时间是 awk 命令的 70%)。看起来cut 在“寻找”文件以到达某一行时速度较慢——但一次处理每一行的效率很高。【参考方案16】:

Perl 解决方案:

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

使用以下命令行选项:

-n循环输入文件的每一行,不要自动打印每一行

-l 在处理之前删除换行符,然后将它们添加回来

-a 自动拆分模式 – 将输入行拆分到 @F 数组中。默认为空格分割

-e执行perl代码

splice @F,0,1 从@F 数组中干净地删除第 0 列

join " ",@F 连接 @F 数组的元素,在每个元素之间使用空格


Python 解决方案:

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

【讨论】:

【参考方案17】:

您可以使用 for 循环循环打印字段 $2 到 $NF(表示行中字段数的内置变量)。

编辑: 由于“打印”附加了一个换行符,因此您需要缓冲结果:

awk 'out=""; for(i=2;i<=NF;i++)out=out" "$i; print out'

或者,使用 printf:

awk 'for(i=2;i<=NF;i++)printf "%s ", $i; printf "\n"'

【讨论】:

所以我尝试了这个,但认为我遗漏了一些东西.. 这是我所做的 svn status | grep '\!' | gawk 'for (i=1; i removedProjs 由于 print 附加了一个换行符,因此您需要缓冲结果。查看我的编辑。 我更喜欢这个答案,因为它展示了如何遍历字段。 如果要打印使用空格,更改输出记录分隔符: awk 'ORS=" "; for(i=2;i 总会有一些空格太多。这样效果更好:'for(i=11;i&lt;=NF-1;i++)printf "%s ", $i; print $NF;' 没有前导或尾随空格。【参考方案18】:

如果您需要使用任意分隔符打印特定列:

awk 'print $3 "  " $4'

col#3 col#4

awk 'print $3 "anything" $4'

col#3anythingcol#4

因此,如果您在一列中有空格,它将是两列,但您可以使用任何分隔符连接它,也可以不使用它。

【讨论】:

【参考方案19】:

这行得通吗?

awk 'print substr($0,length($1)+1);' < file

它在前面留下了一些空白。

【讨论】:

【参考方案20】:

我亲自尝试了上面提到的所有答案,但其中大多数都有些复杂或不正确。从我的角度来看,最简单的方法是:

awk -F" " ' for (i=4; i<=NF; i++) print $i '

    其中 -F" " 定义 awk 使用的分隔符。在我的例子中是空格,它也是 awk 的默认分隔符。这意味着 -F" " 可以忽略。

    NF 定义字段/列的总数。因此循环将从第 4 个字段开始直到最后一个字段/列。

    其中 $N 检索第 N 个字段的值。因此 print $i 将根据循环计数打印当前字段/列。

【讨论】:

问题,将每个字段打印在不同的行上。 没有什么能阻止你在最后添加这个:-) ` | tr '\n' ' ' ` 有点晚了但是 awk ' for (i = 5; i 【参考方案21】:

打印从 #2 开始的列(输出开头没有尾随空格):

ls -l | awk 'sub(/[^ ]+ /, ""); print $0'

【讨论】:

很好,尽管您应该在空格后添加+,因为字段可能被超过 1 个空格分隔(awk 将多个相邻空格视为单个分隔符)。此外,awk 将忽略前导空格,因此您应该以^[ ]* 开始正则表达式。使用空间作为分隔符,您甚至可以概括解决方案;例如,以下内容从第三个字段返回所有内容:awk 'sub(/^[ ]*([^ ]+ +)2/, ""); print $0' 不过,使用任意字段分隔符会变得更加棘手。【参考方案22】:

这是我在所有建议中的首选:

从第 6 列到最后一列打印。

ls -lthr | awk 'out=$6; for(i=7;i<=NF;i++)out=out" "$i; print out'

ls -lthr | awk 'ORS=" "; for(i=6;i<=NF;i++) print $i;print "\n"'

【讨论】:

【参考方案23】:
echo "1 2 3 4 5 6" | awk ' $NF = ""; print $0'

这个使用 awk 打印除最后一个字段之外的所有字段

【讨论】:

【参考方案24】:
awk 'out=$2; for(i=3;i<=NF;i++)out=out" "$i; print out'

我的答案是基于the one of VeeArr,但我注意到它在打印第二列(以及其余列)之前以空​​格开头。由于我只有 1 个声望点,我无法对此发表评论,所以这里作为一个新答案:

以“out”作为第二列,然后添加所有其他列(如果存在)。只要有第二列,这就很好。

【讨论】:

太好了,您还删除了 out 变量前面的 $,这也很重要。

以上是关于使用 awk 打印从第 n 到最后的所有列的主要内容,如果未能解决你的问题,请参考以下文章

awk 拆分更多列并打印第一个单词

如何将第三列打印到最后一列?

awk输出指定列

awk查找指定行指定列的数据 并输出到文件

awk查找指定行指定列的数据 并输出到文件

顺时针和逆时针螺旋打印二维数组(行列式)