【问题标题】:Using awk to print all columns from the nth to the last使用 awk 打印从第 n 到最后的所有列
【发布时间】: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
  • @tripleee:我很高兴你提到了这一点 - 我很沮丧地看到它无处不在!

标签: linux awk


【解决方案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 answerls -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 Koknatperl 解决方案相比,优势在于实际上只有前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 没有:stackoverflow.com/questions/14360640/…
                      • 很好很简单,但有一个警告:awk 处理多个相邻的空格字符。作为 single 分隔符,而 cut 没有;另外——尽管这在手头的情况下不是问题——cut 只接受一个单一的文字字符。作为分隔符,而 awk 允许使用正则表达式。
                      • 基于此:stackoverflow.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 }'
                            
                            1. 其中 -F" " 定义 awk 使用的分隔符。在我的例子中是空格,它也是 awk 的默认分隔符。这意味着 -F" " 可以忽略。

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

                            3. 其中 $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 变量前面的 $,这也很重要。
                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2023-02-04
                                • 2014-03-24
                                • 2017-04-23
                                • 2022-11-16
                                • 2011-05-17
                                • 2022-09-23
                                相关资源
                                最近更新 更多