在文本文件中获取第n列

102

我有一个文本文件:

1 Q0 1657 1 19.6117 Exp
1 Q0 1410 2 18.8302 Exp
2 Q0 3078 1 18.6695 Exp
2 Q0 2434 2 14.0508 Exp
2 Q0 3129 3 13.5495 Exp

我希望提取每行的第二个和第四个单词,如下所示:

1657 19.6117
1410 18.8302
3078 18.6695
2434 14.0508
3129 13.5495

我正在使用这段代码:

 nol=$(cat "/path/of/my/text" | wc -l)
 x=1
 while  [ $x -le "$nol" ]
 do
     line=($(sed -n "$x"p /path/of/my/text)
     echo ""${line[1]}" "${line[3]}""  >> out.txt
     x=$(( $x + 1 ))
 done

它能工作,但非常复杂,处理长文本文件需要很长时间。

有没有更简单的方法?


1
每行的第二个单词称为第二列! - Bernard
6个回答

154
我记得是:
cat filename.txt | awk '{ print $2 $4 }'

或者,如评论所述:

awk '{ print $2 $4 }' filename.txt

19
UUOC!!!awk '{print $2,$4}' filename.txt 更好(不需要管道,只用一个程序) - blue
6
我经常在我的Bash脚本中使用cat而不是指定文件名,因为开销很小,并且语法cat ... | ... > ...可以很好地显示输入和输出的位置。但是你说得对,这里实际上并不需要用到它。 - Tom van der Woerdt
8
有时候我会写 < input awk '{ print $2 $4 }' > output 来达到某个目的。 - ruakh
此外,使用管道或I/O重定向,您可以确保被执行的程序不会修改您的文件。 - fepzzz

76
你可以使用cut命令:
cut -d' ' -f3,5 < datafile.txt

打印

1657 19.6117
1410 18.8302
3078 18.6695
2434 14.0508
3129 13.5495

以下是翻译内容:

  • -d' ' - 表示使用空格作为分隔符。
  • -f3,5 - 表示取出并打印第三和第五列。

cut 命令在处理大文件时比纯 Shell 解决方案要快得多。如果你的文件中包含多个空格作为分隔符,可以先删除它们,例如:

sed 's/[\t ][\t ]*/ /g' < datafile.txt | cut -d' ' -f3,5

(GNU) sed将替换任何tabspace字符为一个单一的space

对于变体 - 这里也有一个perl解决方案:

perl -lanE 'say "$F[2] $F[4]"' < datafile.txt

2
如果每行的空格数量是保证的,那么这个程序可以很好地工作... :) - rogerdpack

27

为了完整性:

while read -r _ _ one _ two _; do
    echo "$one $two"
done < file.txt

可以使用任意变量(如 junk)代替 _。重点是提取列。

示例:

$ while read -r _ _ one _ two _; do echo "$one $two"; done < /tmp/file.txt
1657 19.6117
1410 18.8302
3078 18.6695
2434 14.0508
3129 13.5495

好的,易读的,不需要使用perl, awk或其他语言,在一个shell脚本中就可以实现。 - Petr Matousu
1
使用 read -r 命令来去除 \\ 的转义特性。 - Tom Hale
已添加,谢谢 @TomHale - Johannes Weiss

9

还有一种简单的变体 -

$ while read line
  do
      set $line          # assigns words in line to positional parameters
      echo "$3 $5"
  done < file

使用read -r来去除\\的转义质量。 - Tom Hale

4
如果您的文件包含n行,则您的脚本必须读取文件n次;因此,如果您将文件长度加倍,则脚本执行的工作量将增加四倍 - 而几乎所有这些工作都被简单地丢弃了,因为您只想按顺序循环遍历行。
相反,循环遍历文件行的最佳方法是使用while循环,条件命令是内置的read命令:
while IFS= read -r line ; do
    # $line is a single line of the file, as a single string
    : ... commands that use $line ...
done < input_file.txt

在您的情况下,由于您想将该行分割为数组,并且 read 内置实际上具有特殊支持以填充数组变量,这正是您想要的,您可以编写:

while read -r -a line ; do
    echo ""${line[1]}" "${line[3]}"" >> out.txt
done < /path/of/my/text

或者更好的做法是:
while read -r -a line ; do
    echo "${line[1]} ${line[3]}"
done < /path/of/my/text > out.txt

然而,对于你要做的事情,你可以使用cut工具:

cut -d' ' -f2,4 < /path/of/my/text > out.txt

(或者像Tom van der Woerdt建议的那样使用awk,或者使用perl,甚至使用sed。)

我更喜欢使用read而不是cut,因为它可以很好地处理字段之间的多个空格,并且您不需要使用数组操作:while read word1 word2 word3 word4 rest; do doSomethingWith $word2 $word4; done - user829755

3
如果您正在使用结构化数据,这将有额外的好处,不需要调用额外的shell进程来运行tr和/或cut等命令。当然,您需要通过条件语句和合理的替代方案来防范不良输入。
...
while read line ; 
do 
    lineCols=( $line ) ;
    echo "${lineCols[0]}"
    echo "${lineCols[1]}"
done < $myFQFileToRead ; 
...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接