【问题标题】:How to find the last field using 'cut'如何使用'cut'找到最后一个字段
【发布时间】:2014-05-08 18:10:54
【问题描述】:

不使用使用sedawkcut,当字段数未知或每次更改时如何获取最后一个字段行吗?

【问题讨论】:

  • 你爱上了cut命令:)吗?为什么没有其他 Linux 命令?
  • 没有sedawkperl -pe 's/^.+\s+([^\s]+)$/$1/'
  • @MestreLion 很多时候,人们阅读问题是为了找到问题变体的解决方案。这个错误的前提是cut 支持它不支持的东西。但我认为它很有用,因为它迫使读者考虑更容易理解的代码。我想要一种快速、简单的方法来使用cut,而无需为awkgrepsed 等使用多种语法。rev 做到了这一点;非常优雅,而且我从未考虑过(即使在其他情况下很笨重)。我也喜欢从其他答案中阅读其他方法。
  • 遇到了一个现实生活中的问题:我想在源代码树中找到所有不同的文件扩展名,以更新 .gitattributes 文件。所以find | cut -d. -f<last>是自然倾向

标签: linux bash cut


【解决方案1】:

你可以试试这样的:

echo 'maps.google.com' | rev | cut -d'.' -f 1 | rev

说明

  • rev 将“maps.google.com”反转为 moc.elgoog.spam
  • cut 使用点(即'.')作为分隔符,并选择第一个字段,即moc
  • 最后,我们再次反转得到com

【讨论】:

  • 它不仅使用cut,而且没有使用sedawk。那么OP怎么想?
  • @tom OP 在过去几个小时内提出的问题不止于此。根据我们与 OP 的交互,我们知道 awk/sed/etc.不允许在他的作业中使用,但没有提到 rev。所以值得一试
  • @zfus 我明白了。之后可能想再贴一个rev
  • double rev 伟大的理想!
  • 真棒,简单,完美,也感谢您的解释 - 没有足够的人解释长链管道命令中的每一步
【解决方案2】:

使用参数扩展。这比任何类型的外部命令都高效得多,包括cut(或grep)。

data=foo,bar,baz,qux
last=${data##*,}

请参阅BashFAQ #100 了解 bash 中的原生字符串操作介绍。

【讨论】:

  • @ErwinWessels:因为 bash 真的很慢。使用 bash 运行管道,而不是批量处理数据。我的意思是,如果您在 shell 变量中已经有一行文本,或者如果您想使用 while IFS= read -ra array_var; do :;done <(cmd) 来处理几行,这很好。但是对于大文件,rev|cut|rev 可能更快! (当然 awk 会比这更快。)
  • @PeterCordes,awk 对于大文件会更快,当然,但需要相当多的输入来克服恒定因素的启动成本。 (还有一些 shell——比如 ksh93——性能更接近于 awk,这个答案中给出的语法仍然有效;bash 异常缓慢,但它甚至不接近唯一可用的选项)。
  • 谢谢@PeterCordes;像往常一样,我猜每个工具都有其用例。
  • 这是迄今为止在bash 脚本中缩减单个变量的最快、最简洁的方法(假设您已经在使用bash 脚本)。无需调用外部任何东西。
  • @Balmipour, ...但是,rev 特定于您使用的任何提供它的操作系统 - 它不是在所有 UNIX 系统中标准化的。请参阅chapter listing for the POSIX section on commands and utilities——它不存在。 ${var##prefix_pattern} 实际上是 not 特定于 bash 的;它在POSIX sh standard 中,请参阅第 2.6.2 节的末尾(链接),因此与rev 不同,它始终可以在任何兼容的外壳上使用。
【解决方案3】:

仅使用cut 是不可能的。这是使用grep的一种方式:

grep -o '[^,]*$'

替换其他分隔符的逗号。

说明:

  • -o (--only-matching) 仅输出与模式匹配的输入部分(如果包含匹配项,则默认打印整行)。
  • [^,] 是一个 character class,它匹配除逗号之外的任何字符。
  • * 匹配前面的模式零次或多次,因此[^,]* 匹配零次或多个非逗号字符。
  • $ 匹配字符串的结尾。
  • 综合起来,该模式匹配字符串末尾的零个或多个非逗号字符。
  • 当有多个可能的匹配时,grep 更喜欢最早开始的那个。因此,整个最后一个字段将被匹配。

完整示例:

如果我们有一个名为 data.csv 的文件包含

one,two,three
foo,bar

然后grep -o '[^,]*$' < data.csv 将输出

three
bar

【讨论】:

  • 反之,查找除最后一个字段之外的所有内容:grep -o '^.*,'
  • 这特别有用,因为 rev 在我的情况下添加了一个问题多字节 unicode 字符。
  • 我试图在 MinGW 上执行此操作,但我的 grep 版本不支持 -o,因此我使用了sed 's/^.*,//',它将所有字符替换为空字符串,包括最后一个逗号。
【解决方案4】:

没有 awk ?... 但是 awk 就是这么简单:

echo 'maps.google.com' | awk -F. '{print $NF}'

AWK 是一种更强大的工具,可以放在你的口袋里。 -F 如果用于字段分隔符 NF是字段的个数(也代表最后一个的索引)

【讨论】:

  • 这是通用的,每次都能完全按照预期工作。在这种情况下,使用cut 实现 OP 的最终输出就像使用勺子“切”牛排(双关语:))。 awk 是牛排刀。
  • 避免不必要地使用echo,这可能会减慢使用awk -F. '{print $NF}' <<< 'maps.google.com'的长文件的脚本。
【解决方案5】:

有多种方法。你也可以用这个。

echo "Your string here"| tr ' ' '\n' | tail -n1
> here

显然,tr 命令的空格输入应该替换为您需要的分隔符。

【讨论】:

  • 这对我来说感觉是最简单的答案,更少的管道和更清晰的含义
  • 这不适用于整个文件,这可能是 OP 的意思。
【解决方案6】:

这是唯一可能只使用剪切的解决方案:

回显“s.t.r.i.n.g.” |切-d'。 -f2- [repeat_following_part_forever_or_until_out_of_memory:] |切-d'。 -f2-

使用此解决方案,字段的数量确实是未知的,并且会不时变化。但是,由于行长不得超过 LINE_MAX 个字符或字段,包括换行符,因此任意数量的字段永远不会成为此解决方案的真实条件。

是的,一个非常愚蠢的解决方案,但唯一符合我认为的标准的解决方案。

【讨论】:

  • 不错。只需取最后一个“。”关闭“s.t.r.i.n.g.”这行得通。
  • 我喜欢每个人都说某事是不可能的,然后有人附和一个可行的答案。哪怕确实很傻。
  • 可以循环迭代cut -f2-,直到输出不再变化。
  • 我认为您必须逐行读取文件并然后迭代cut -f2-,直到它不再更改。否则你必须缓冲整个文件。
【解决方案7】:

如果您的输入字符串不包含正斜杠,那么您可以使用basename 和一个子shell:

$ basename "$(echo 'maps.google.com' | tr '.' '/')"

这不使用sedawk,但它也没有使用cut,所以我不太确定它是否有资格作为问题的答案。

如果处理可以包含正斜杠的输入字符串,这将无法正常工作。这种情况的解决方法是将正斜杠替换为您知道不是有效输入字符串的一部分的其他字符。例如,管道 (|) 字符也不允许出现在文件名中,因此可以这样做:

$ basename "$(echo 'maps.google.com/some/url/things' | tr '/' '|' | tr '.' '/')" | tr '|' '/'

【讨论】:

  • 当然管道字符可以在文件名中使用。试试touch \|
  • 如果您删除关于文件名中不允许使用 | 的虚假声明,我将从反对票改为赞成票。但是几乎所有的tr 都支持\0 或其他表达nul 字节的方式,这绝对不允许在文件名中使用,因此您可以将其用作占位符。同样tr ab bc 只是交换所有ab 没有问题,所以你可以避免完全找到一个不允许的字符。只需通过tr './' './'basename 之前交换一次,然后在之后再次交换回来。
  • 刚刚意识到我有一个错字:“只需通过 tr '/.' './' 管道一次以在基本名称之前交换,然后在之后再次交换”。
【解决方案8】:

以下实现A friend's suggestion

#!/bin/bash
rcut(){

  nu="$( echo $1 | cut -d"$DELIM" -f 2-  )"
  if [ "$nu" != "$1" ]
  then
    rcut "$nu"
  else
    echo "$nu"
  fi
}

$ export DELIM=.
$ rcut a.b.c.d
d

【讨论】:

【解决方案9】:

使用 perl 的替代方法是:

perl -pe 's/(.*) (.*)$/$2/' file

您可以在哪里更改\tfile 的分隔符

【讨论】:

    【解决方案10】:

    如果您有一个名为 filelist.txt 的文件,它是一个列表路径,如下所示: c:/dir1/dir2/file1.h c:/dir1/dir2/dir3/file2.h

    那么你可以这样做: rev 文件列表.txt |剪切 -d"/" -f1 |转

    【讨论】:

      【解决方案11】:

      为这个老问题添加一个方法只是为了好玩:

      $ cat input.file # file containing input that needs to be processed
      a;b;c;d;e
      1;2;3;4;5
      no delimiter here
      124;adsf;15454
      foo;bar;is;null;info
      
      $ cat tmp.sh # showing off the script to do the job
      #!/bin/bash
      delim=';'
      while read -r line; do  
          while [[ "$line" =~ "$delim" ]]; do
              line=$(cut -d"$delim" -f 2- <<<"$line")
          done
          echo "$line"
      done < input.file
      
      $ ./tmp.sh # output of above script/processed input file
      e
      5
      no delimiter here
      15454
      info
      

      除了 bash,只使用了 cut。 嗯,我猜是回声。

      【讨论】:

      • 嗯,为什么不完全删除 cut 而只使用 bash... x] while read -r line; do echo ${line/*;}; done &lt;input.file 产生相同的结果。
      【解决方案12】:

      我意识到,如果我们只是确保存在尾随分隔符,它就可以工作。所以就我而言,我有逗号和空格分隔符。我在末尾添加了一个空格;

      $ ans="a, b"
      $ ans+=" "; echo ${ans} | tr ',' ' ' | tr -s ' ' | cut -d' ' -f2
      b
      

      【讨论】:

      • ans="a, b, c"产生b,不符合“字段数未知或每行变化”的要求。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多