交换两列 - awk,sed,python,perl

76

我有一个较大的文件(280列,700万行!),需要交换第一列和第二列。我认为可以使用awk循环打印$2,$1,然后是文件的结尾范围 - 但我不知道如何进行区间操作,并且我不能打印$2,$1,$3 ... $280!我在这里看到的大多数列交换答案都是特定于具有可管理列数的小文件的,因此我需要一些不依赖于指定每个列号的东西。

文件使用制表符分隔:

Affy-id chr 0 pos NA06984 NA06985 NA06986 NA06989
10个回答

127
您可以通过交换前两个字段的值来实现此操作:
awk ' { t = $1; $1 = $2; $2 = t; print; } ' input_file

5
这个答案在处理不同列大小和它们的分隔符时存在问题。更具扩展性的答案在这里http://unix.stackexchange.com/a/31596/16920。 - Léo Léopold Hertz 준영
4
使用 -F '\t' 时,制表符在最终输出中被吞掉了。有没有方法可以保留它们? - Atcold
3
好的,正如下面的答案所指出的那样,必须指定 OFS=$'\t'。@perreal,也许值得更新答案以包含附加参数? - Atcold
14
如果你使用命令:awk '{ print $2, $1}' 它的意思是相同的。 - A.Villegas
2
@cornuz,没问题。请注意,您的建议只打印了2列。OP想要打印所有列,而不仅仅是前两列。 - perreal
显示剩余7条评论

26

我在Windows系统上尝试使用Cygwin和一个制表符分隔的文件来尝试perreal的答案,但它无法正常工作,因为标准分隔符是空格。

如果你遇到相同的问题,可以尝试这个方法:

awk -F $'\t' ' { t = $1; $1 = $2; $2 = t; print; } ' OFS=$'\t' input_file

输入分隔符由-F $'\t'定义,输出分隔符由OFS=$'\t'定义。

awk -F $'\t' ' { t = $1; $1 = $2; $2 = t; print; } ' OFS=$'\t' input_file > output_file

2
太好了!我忘记加上 OFS=$'\t' 参数了! - Atcold
3
这种方法可能会导致行首出现制表符,这可能不是预期的结果。 - Kenny Powers

13
尝试使用以下更相关的内容来回答您的问题:
awk '{printf("%s\t%s\n", $2, $1)}' inputfile

9
这个命令只会打印前两列。更简洁的方式是使用 awk '{print $2 "\t" $1}' inputfile - Fuujuhi

6
这可能适用于您(GNU sed):
sed -i 's/^\([^\t]*\t\)\([^\t]*\t\)/\2\1/' file

对于我们Vim用户来说,这是完美的解决方案。 - awm

3

您尝试过使用cut命令吗?例如:

cat myhugefile | cut -c10-20,c1-9,c21- > myrearrangedhugefile

我还没有,但我会记住这个以备将来使用! - Charley Farley
3
-c=characters ... 所以不交换列。 - blehman
它将在输出文件中交换列 - 请自行尝试。 - Robbie Dee
5
不知道字符数怎么办? cat myhugefile | cut -f2,1 的输出结果与 cat myhugefile | cut -f1,2 相同。 - Hady Elsahar
5
您可以将每列输出到一个中间文件。例如:cut -f2 myhugefile > piece1 ; cut -f1 myhugefile > piece2 | paste piece1 piece2 > myrearrangedhugefile ; rm piece1 ; rm piece2 - Robbie Dee

3
这在 Perl 中也很简单:
perl -pe 's/^(\S+)\t(\S+)/$2\t$1/;' file > outputfile

2
您可以使用Perl来完成这项任务:
perl -F\\t -nlae 'print join("\t", @F[1,0,2..$#F])' inputfile
-F参数指定分隔符。在大多数shell中,您需要在反斜杠前面再加一个反斜杠进行转义。在某些平台上,-F自动意味着-n-a,因此它们可以省略。
对于您的问题,您不需要使用-l,因为最后一列出现在输出的最后。但如果在不同的情况下,如果最后一列需要出现在其他列之间,则必须删除换行符。 -l开关会处理这个问题。
在join命令中的"\t"可以更改为任何其他内容,以在输出中生成不同的分隔符。 2..$#F指定从第2列到最后一列的范围。正如您可能已经猜到的那样,在方括号内,您可以按所需顺序放置任何单个列或列范围。

2
不需要调用其他任何东西,只需使用你的Shell:
bash> while read col1 col2 rest; do 
        echo $col2 $col1 $rest
      done <input_file

测试:

bash> echo "first second a c d e f g" | 
      while read col1 col2 rest; do 
        echo $col2 $col1 $rest
      done
second first a b c d e f g

0

awk 无需使用临时变量进行交换:

echo '777777744444444464449: 317 647 14423 262927714037  :   0x2A29D5A1BAA7A95541' | 
mawk '1; ($1 = $2 substr(_, ($2 = $1)^_))^_' FS=':' OFS=':' 
777777744444444464449: 317 647 14423 262927714037  :   0x2A29D5A1BAA7A95541

 317 647 14423 262927714037  :777777744444444464449:   0x2A29D5A1BAA7A95541

0
也许甚至可以使用内联 Python - 就像在 shell 脚本中的 Python 脚本一样 - 但前提是您想要在之前或之后使用 Bash 进行更多脚本编写...否则这将变得不必要复杂。
脚本文件process.sh的内容:
#!/bin/bash

# inline Python script
read -r -d '' PYSCR << EOSCR
from __future__ import print_function
import codecs
import sys

encoding = "utf-8"
fn_in = sys.argv[1]
fn_out = sys.argv[2]

# print("Input:", fn_in)
# print("Output:", fn_out)

with codecs.open(fn_in, "r", encoding) as fp_in, \
        codecs.open(fn_out, "w", encoding) as fp_out:
    for line in fp_in:
        # split into two columns and rest
        col1, col2, rest = line.split("\t", 2)
        # swap columns in output
        fp_out.write("{}\t{}\t{}".format(col2, col1, rest))
EOSCR

# ---------------------
# do setup work?
# e. g. list files for processing

# call python script with params
python3 -c "$PYSCR" "$inputfile" "$outputfile"

# do some more processing
# e. g. rename outputfile to inputfile, ...

如果您只需要为单个文件交换列,则可以创建一个单独的Python脚本并静态定义文件名。或者,您也可以使用上面的答案。

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