【问题标题】:Printing everything except the first field with awk使用 awk 打印除第一个字段之外的所有内容
【发布时间】:2011-05-11 00:48:32
【问题描述】:

我有一个如下所示的文件:

AE  United Arab Emirates
AG  Antigua & Barbuda
AN  Netherlands Antilles
AS  American Samoa
BA  Bosnia and Herzegovina
BF  Burkina Faso
BN  Brunei Darussalam

我想颠倒顺序,首先打印除 $1 之外的所有内容,然后打印 $1:

United Arab Emirates AE

我怎样才能完成“除了字段 1 之外的所有事情”的技巧?

【问题讨论】:

  • 嗨@cfisher,它可以在没有额外空间的情况下完成。
  • 这个问题的表述有点误导。我的两分钱:“如何将第一个字段移动到 awk 中的最后一个位置”

标签: awk sed


【解决方案1】:

$1="" 留下了 Ben Jackson 提到的空格,所以使用 for 循环:

awk '{for (i=2; i<=NF; i++) print $i}' filename

所以如果你的字符串是“一二三”,输出将是:

两个

如果您希望结果在一行中,您可以执行以下操作:

awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}' filename

这会给你:“二三”

【讨论】:

  • 和一个额外的尾随空格
  • 更好用:awk '{for(i=2;i&lt;=NF;i++){ printf("%s",( (i&gt;2) ? OFS : "" ) $i) } ; print ;}' which :将字段 2 打印到 NF,根据需要添加输出字段分隔符(即,$2 之前除外)。最后一次打印添加最后一个换行符以结束当前行打印。如果您更改 FS/OFS(即,它并不总是“空格”),那一个会起作用
  • 第二个对我来说真的很好用。第一个,不多。不太清楚为什么。它切掉了整个文本。
【解决方案2】:

分配$1 有效,但会留下前导空格:awk '{first = $1; $1 = ""; print $0, first; }'

您还可以在NF 中找到列数并在循环中使用它。

【讨论】:

  • 对于完全懒惰的人;这里是klashxx' code
  • 太棒了。用 sed 去掉了前导空格:awk {'first = $1; $1=""; print $0'}|sed 's/^ //g'
  • VIM 在普通模式下按 'Ctrl+V Gd' 可以轻松删除空格
  • 要删除前导空格,您也可以使用 gsub :awk '/&gt;/ {first = $1; $1=""; gsub(/^ /,""); print $0, first}' somefile
【解决方案3】:

cut 命令与-f 2-(POSIX)或--complement(非POSIX)一起使用:

$ echo a b c | cut -f 2- -d ' '
b c
$ echo a b c | cut -f 1 -d ' '
a
$ echo a b c | cut -f 1,2 -d ' '
a b
$ echo a b c | cut -f 1 -d ' ' --complement
b c

【讨论】:

  • 虽然没有回答特定于 awk 的问题,但我发现这最有用,因为 awk 删除了重复的空格,而 cut 没有。
  • echo a b c | cut -d' ' -f 2- 是另一种选择
  • 很好 - @Luis 解决方案适用于不支持 --complement 的 Mac 上
【解决方案4】:

也许是最简洁的方式:

$ awk '{$(NF+1)=$1;$1=""}sub(FS,"")' infile
United Arab Emirates AE
Antigua & Barbuda AG
Netherlands Antilles AN
American Samoa AS
Bosnia and Herzegovina BA
Burkina Faso BF
Brunei Darussalam BN

解释:

$(NF+1)=$1:“新”最后一个字段的生成器。

$1="":将原来的第一个字段设置为空

sub(FS,""):在前两个操作之后{$(NF+1)=$1;$1=""} 使用 sub 去掉第一个字段分隔符。最后的打印是隐式的。

【讨论】:

  • 也许我遗漏了一些东西,但这在 gawk 5.1 上对我不起作用——它只是将第一个字段移动到打印的末尾而不是忽略它。
  • 那是因为问题的表述具有误导性@SorenBjornstad。应该是:“如何将第一个字段移动到最后一个位置”
【解决方案5】:
awk '{sub($1 FS,"")}7' YourFile

删除第一个字段和分隔符,并打印结果(7 是非零值,因此打印 $0)。

【讨论】:

  • 最佳答案!赞成。与仅使用1 有何不同?我想知道这种模式的用法并想了解这一点。谢谢!
【解决方案6】:
awk '{ saved = $1; $1 = ""; print substr($0, 2), saved }'

将第一个字段设置为"" 会在$0 的开头留下OFS 的一个副本。假设OFS只有一个字符(默认是一个空格),我们可以用substr($0, 2)去掉它。然后我们追加$1的保存副本。

【讨论】:

    【解决方案7】:

    如果您愿意接受 Perl 解决方案...

    perl -lane 'print join " ",@F[1..$#F,0]' file
    

    是一个简单的解决方案,输入/输出分隔符为一个空格,产生:

    United Arab Emirates AE
    Antigua & Barbuda AG
    Netherlands Antilles AN
    American Samoa AS
    Bosnia and Herzegovina BA
    Burkina Faso BF
    Brunei Darussalam BN
    

    下一个稍微复杂一些

    perl -F`  ` -lane 'print join "  ",@F[1..$#F,0]' file
    

    并假设输入/输出分隔符是两个空格:

    United Arab Emirates  AE
    Antigua & Barbuda  AG
    Netherlands Antilles  AN
    American Samoa  AS
    Bosnia and Herzegovina  BA
    Burkina Faso  BF
    Brunei Darussalam  BN
    

    使用以下命令行选项:

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

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

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

    • -F autosplit 修饰符,在此示例中拆分为 ' '(两个空格)

    • -e执行以下perl代码

    @F 是每行中的单词数组,从 0 开始索引
    $#F@F 中的单词数
    @F[1..$#F] 是元素 1 到最后一个元素的数组切片element
    @F[1..$#F,0] 是元素 1 到最后一个元素加上元素 0 的数组切片

    【讨论】:

    • 我运行它,最后有一个额外的数字,所以我使用了这个版本: perl -lane 'shift @F;打印加入“”,@F'
    【解决方案8】:

    gawk 中的字段分隔符(至少)可以是字符串也可以是字符(也可以是正则表达式)。如果您的数据是一致的,那么这将起作用:

    awk -F "  " '{print $2,$1}' inputfile
    

    这是双引号之间的两个空格。

    【讨论】:

    • 当前情况的最佳答案,但从技术上讲,这并不能回答如何打印除第一个字段之外的所有内容的问题。
    • @DanMoulding:只要文件在使用两个空格分隔国家代码方面是一致的,并且没有其他两个空格一起出现,我的回答可以解决问题。
    • 遇到这个问题的人来到这里是因为他们想知道如何打印除第一个字段以外的所有内容(请参阅问题标题)。我就是这样降落在这里的。您的答案显示了如何打印第一个字段,然后是第二个字段。虽然这可能是针对 OP 特定情况的最佳解决方案,但它并不能解决如何打印除第一个字段之外的所有内容的一般问题。
    【解决方案9】:

    awk '{ tmp = $1; sub(/^[^ ]+ +/, ""); print $0, tmp }'

    【讨论】:

      【解决方案10】:

      让我们将所有记录移到下一条,并将最后一条设置为第一条:

      $ awk '{a=$1; for (i=2; i<=NF; i++) $(i-1)=$i; $NF=a}1' file
      United Arab Emirates AE
      Antigua & Barbuda AG
      Netherlands Antilles AN
      American Samoa AS
      Bosnia and Herzegovina BA
      Burkina Faso BF
      Brunei Darussalam BN
      

      说明

      • a=$1 将第一个值保存到临时变量中。
      • for (i=2; i&lt;=NF; i++) $(i-1)=$i 将第 N 个字段值保存到第 (N-1) 个字段中。
      • $NF=a 将第一个值 ($1) 保存到最后一个字段中。
      • {}1 true 条件使awk 执行默认操作:{print $0}

      这样,如果你碰巧有另一个字段分隔符,结果也不错:

      $ cat c
      AE-United-Arab-Emirates
      AG-Antigua-&-Barbuda
      AN-Netherlands-Antilles
      AS-American-Samoa
      BA-Bosnia-and-Herzegovina
      BF-Burkina-Faso
      BN-Brunei-Darussalam
      
      $ awk 'BEGIN{OFS=FS="-"}{a=$1; for (i=2; i<=NF; i++) $(i-1)=$i; $NF=a}1' c
      United-Arab-Emirates-AE
      Antigua-&-Barbuda-AG
      Netherlands-Antilles-AN
      American-Samoa-AS
      Bosnia-and-Herzegovina-BA
      Burkina-Faso-BF
      Brunei-Darussalam-BN
      

      【讨论】:

        【解决方案11】:

        如果您愿意接受另一个 Perl 解决方案:

        perl -ple 's/^(\S+)\s+(.*)/$2 $1/' file
        

        【讨论】:

          【解决方案12】:

          第一次尝试似乎适用于您的特定情况。

          awk '{ f = $1; i = $NF; while (i <= 0); gsub(/^[A-Z][A-Z][ ][ ]/,""); print $i, f; }'
          

          【讨论】:

            【解决方案13】:

            选项 1

            有一个适用于某些版本的 awk 的解决方案:

            awk '{ $(NF+1)=$1;$1="";$0=$0;} NF=NF ' infile.txt
            

            解释:

                   $(NF+1)=$1                          # add a new field equal to field 1.
                              $1=""                    # erase the contents of field 1.
                                    $0=$0;} NF=NF      # force a re-calc of fields.
                                                       # and use NF to promote a print.
            

            结果:

            United Arab Emirates AE
            Antigua & Barbuda AG
            Netherlands Antilles AN
            American Samoa AS
            Bosnia and Herzegovina BA
            Burkina Faso BF
            Brunei Darussalam BN
            

            但是,旧版本的 awk 可能会失败。


            选项 2

            awk '{ $(NF+1)=$1;$1="";sub(OFS,"");}1' infile.txt
            

            即:

            awk '{                                      # call awk.
                   $(NF+1)=$1;                          # Add one trailing field.
                              $1="";                    # Erase first field.
                                    sub(OFS,"");        # remove leading OFS.
                                                }1'     # print the line.
            

            请注意,需要擦除的是 OFS,而不是 FS。分配字段 $1 时,将重新计算该行。这会将 FS 的所有运行更改为一个 OFS。


            但即使是该选项仍然会因多个分隔符而失败,正如更改 OFS 所清楚表明的那样:

            awk -v OFS=';' '{ $(NF+1)=$1;$1="";sub(OFS,"");}1' infile.txt
            

            该行将输出:

            United;Arab;Emirates;AE
            Antigua;&;Barbuda;AG
            Netherlands;Antilles;AN
            American;Samoa;AS
            Bosnia;and;Herzegovina;BA
            Burkina;Faso;BF
            Brunei;Darussalam;BN
            

            这表明 FS 的运行正在更改为一个 OFS。
            避免这种情况的唯一方法是避免重新计算字段。
            一个可以避免重新计算的函数是 sub。
            可以捕获第一个字段,然后使用 sub 从 $0 中删除,然后重新打印。

            选项 3

            awk '{ a=$1;sub("[^"FS"]+["FS"]+",""); print $0, a;}' infile.txt
                   a=$1                                   # capture first field.
                   sub( "                                 # replace: 
                         [^"FS"]+                         # A run of non-FS
                                 ["FS"]+                  # followed by a run of FS.
                                        " , ""            # for nothing.
                                              )           # Default to $0 (the whole line.
                   print $0, a                   # Print in reverse order, with OFS.
            
            
            United Arab Emirates AE
            Antigua & Barbuda AG
            Netherlands Antilles AN
            American Samoa AS
            Bosnia and Herzegovina BA
            Burkina Faso BF
            Brunei Darussalam BN
            

            即使我们更改 FS、OFS 和/或添加更多分隔符,它仍然有效。
            如果输入文件改为:

            AE..United....Arab....Emirates
            AG..Antigua....&...Barbuda
            AN..Netherlands...Antilles
            AS..American...Samoa
            BA..Bosnia...and...Herzegovina
            BF..Burkina...Faso
            BN..Brunei...Darussalam
            

            命令变为:

            awk -vFS='.' -vOFS=';' '{a=$1;sub("[^"FS"]+["FS"]+",""); print $0,a;}' infile.txt
            

            输出将是(仍然保留分隔符):

            United....Arab....Emirates;AE
            Antigua....&...Barbuda;AG
            Netherlands...Antilles;AN
            American...Samoa;AS
            Bosnia...and...Herzegovina;BA
            Burkina...Faso;BF
            Brunei...Darussalam;BN
            

            该命令可以扩展到多个字段,但仅限于现代 awks 和 --re-interval 选项处于活动状态。对原始文件的这个命令:

            awk -vn=2 '{a=$1;b=$2;sub("([^"FS"]+["FS"]+){"n"}","");print $0,a,b;}' infile.txt
            

            会输出这个:

            Arab Emirates AE United
            & Barbuda AG Antigua
            Antilles AN Netherlands
            Samoa AS American
            and Herzegovina BA Bosnia
            Faso BF Burkina
            Darussalam BN Brunei
            

            【讨论】:

              【解决方案14】:

              还有一个 sed 选项...

               sed 's/\([^ ]*\)  \(.*\)/\2 \1/' inputfile.txt
              

              解释...

              Swap
              \([^ ]*\) = Match anything until we reach a space, store in $1
              \(.*\)    = Match everything else, store in $2
              With
              \2        = Retrieve $2
              \1        = Retrieve $1
              

              更详尽的解释...

              s    = Swap
              /    = Beginning of source pattern
              \(   = start storing this value
              [^ ] = text not matching the space character
              *    = 0 or more of the previous pattern
              \)   = stop storing this value
              \(   = start storing this value
              .    = any character
              *    = 0 or more of the previous pattern
              \)   = stop storing this value
              /    = End of source pattern, beginning of replacement
              \2   = Retrieve the 2nd stored value
              \1   = Retrieve the 1st stored value
              /    = end of replacement
              

              【讨论】:

                【解决方案15】:

                另一种方式......

                ...这会将字段 2 到 NF 与 FS 重新连接起来,并且每行输入输出一行

                awk '{for (i=2;i<=NF;i++){printf $i; if (i < NF) {printf FS};}printf RS}'
                

                我将它与 git 一起使用,以查看我的工作目录中已修改哪些文件:

                git diff| \
                    grep '\-\-git'| \
                    awk '{print$NF}'| \
                    awk -F"/" '{for (i=2;i<=NF;i++){printf $i; if (i < NF) {printf FS};}printf RS}'
                

                【讨论】:

                • 这是在我了解 git diff --name-only 之前
                【解决方案16】:

                另一种使用 cat 命令的简单方法

                cat filename | awk '{print $2,$3,$4,$5,$6,$1}' > newfilename
                

                【讨论】:

                • 我投了反对票,因为这不是一种动态方法。有了这个,您需要知道参数的数量并假设您的数据是一致的。数据几乎从来都不是一致的,您的方法大部分时间都必须考虑到这一点。
                猜你喜欢
                • 2011-03-04
                • 1970-01-01
                • 1970-01-01
                • 2022-01-05
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-12-03
                • 2014-04-07
                相关资源
                最近更新 更多