155

我有以下格式的文件

列 1 列 2
str1 1
str2 2
str3 3

我希望重新排列列。我试过下面的命令

剪切 -f2,1 文件.txt

该命令不会对列重新排序。知道为什么它不起作用吗?

4

9 回答 9

175

对于cut(1)手册页:

使用 -b、-c 或 -f 之一,并且只能使用其中之一。每个 LIST 由一个范围组成,或者由逗号分隔的多个范围组成。所选输入的写入顺序与读取顺序相同,并且只写入一次。

它首先到达字段 1,因此被打印,然后是字段 2。

改用awk

awk '{ print $2 " " $1}' file.txt
于 2010-01-24T22:21:35.537 回答
69

你也可以结合cutpaste

paste <(cut -f2 file.txt) <(cut -f1 file.txt)

通过评论:可以通过以下方式避免 bashisms 并删除一个 cut 实例:

paste file.txt file.txt | cut -f2,3
于 2013-02-19T15:52:51.397 回答
7

您可以为此使用 Perl:

perl -ane 'print "$F[1] $F[0]\n"' < file.txt
  • -e 选项表示执行它之后的命令
  • -n 表示逐行读取(打开文件,在本例中为 STDOUT,并循环遍历行)
  • -a 表示将这些行拆分为一个名为 @F 的向量(“F” - 类似于字段)。Perl 从 0 开始索引向量,不像 cut 索引从 1 开始的字段。
  • 您可以添加 -F模式(在 -F 和模式之间没有空格)以在读取文件时使用模式作为字段分隔符,而不是默认的空格

运行 perl 的优点是(如果您了解 Perl)您可以在 F 上进行比重新排列列更多的计算。

于 2014-04-07T17:07:32.243 回答
7

仅使用外壳,

while read -r col1 col2
do
  echo $col2 $col1
done <"file"
于 2010-01-25T00:19:30.820 回答
6

使用join

join -t $'\t' -o 1.2,1.1 file.txt file.txt

笔记:

  • -t $'\t'GNU join中更直观,-t '\t' 没有$失败,(coreutils v8.28及更早版本?);这可能是一个错误,$应该需要像这样的解决方法。请参阅:unix 连接分隔符 char

  • join需要两个文件名,即使只有一个文件正在处理。使用相同的名称两次join可以执行所需的操作。

  • 对于资源join较少的系统,其占用空间比其他答案中使用的一些工具要小:

    wc -c $(realpath `which cut join sed awk perl`) | head -n -1
      43224 /usr/bin/cut
      47320 /usr/bin/join
     109840 /bin/sed
     658072 /usr/bin/gawk
    2093624 /usr/bin/perl
    
于 2018-11-18T20:29:28.430 回答
3

只是在做一些非常相似的事情,我不是专家,但我想我会分享我使用过的命令。我有一个多列 csv,我只需要其中的 4 列,然后我需要重新排序它们。

我的文件是管道'|' 分隔,但可以换出。

LC_ALL=C cut -d$'|' -f1,2,3,8,10 ./file/location.txt | sed -E "s/(.*)\|(.*)\|(.*)\|(.*)\|(.*)/\3\|\5\|\1\|\2\|\4/" > ./newcsv.csv

诚然,它确实很粗糙且准备就绪,但可以对其进行调整以适应!

于 2013-10-10T09:49:32.923 回答
2

就像对建议复制列然后执行的答案的补充一样cut。对于复制paste等仅适用于文件,但不适用于流。在这种情况下,请sed改用。

cat file.txt | sed s/'.*'/'&\t&'/ | cut -f2,3

这适用于文件和流,如果不只是使用 读取文件,而是在重新排列列之前cat做一些有趣的事情,这很有趣。

相比之下,以下方法不起作用:

cat file.txt | paste - - | cut -f2,3

在这里,双标准输入占位符paste不复制标准输入,而是读取下一行。

于 2021-01-04T11:00:53.967 回答
1

使用 sed

将 sed 与基本正则表达式的嵌套子表达式一起使用来捕获列内容并对其重新排序。这种方法最适合用于对列进行重新排序的切割数量有限的情况,例如本例。

\(基本思想是用和包围搜索模式的有趣部分,\)可以在替换模式中回放,\#其中#表示子表达式在搜索模式中的顺序位置。

例如:

$ echo "foo bar" | sed "s/\(foo\) \(bar\)/\2 \1/"

产量:

bar foo

子表达式之外的文本会被扫描,但不会保留以在替换字符串中播放。

尽管该问题没有讨论固定宽度的列,但我们将在这里讨论,因为这是对所提出的任何解决方案的一个有价值的衡量标准。为简单起见,我们假设文件是​​用空格分隔的,尽管解决方案可以扩展到其他分隔符。

折叠空间

为了说明最简单的用法,我们假设多个空格可以折叠成单个空格,并且第二列值以 EOL 终止(而不是空格填充)。

文件:

bash-3.2$ cat f
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  nl
0000040    s   t   r   2  sp  sp  sp  sp  sp  sp  sp   2  nl   s   t   r
0000060    3  sp  sp  sp  sp  sp  sp  sp   3  nl 
0000072

转换:

bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f
Column2 Column1
1 str1
2 str2
3 str3
bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  nl
0000020    1  sp   s   t   r   1  nl   2  sp   s   t   r   2  nl   3  sp
0000040    s   t   r   3  nl
0000045

保留列宽

现在让我们将该方法扩展到具有恒定宽度列的文件,同时允许列具有不同的宽度。

文件:

bash-3.2$ cat f2
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f2
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  sp
0000040   sp  sp  sp  sp  sp  nl   s   t   r   2  sp  sp  sp  sp  sp  sp
0000060   sp   2  sp  sp  sp  sp  sp  sp  nl   s   t   r   3  sp  sp  sp
0000100   sp  sp  sp  sp   3  sp  sp  sp  sp  sp  sp  nl
0000114

转换:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2
Column2 Column1
1       str1      
2       str2      
3       str3      
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   2  sp  sp  sp  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

最后,虽然问题的示例没有长度不等的字符串,但这个 sed 表达式支持这种情况。

文件:

bash-3.2$ cat f3
Column1    Column2
str1       1      
string2    2      
str3       3      

转换:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3
Column2 Column1   
1       str1      
2       string2   
3       str3    
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   i   n   g   2  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

与 shell 下其他列重新排序方法的比较

  • 令人惊讶的是,对于文件操作工具来说,awk 并不适合从字段剪切到记录末尾。在 sed 中,这可以使用正则表达式来完成,例如匹配列的表达式\(xxx.*$\)在哪里。xxx

  • 在实现内部 shell 脚本时,使用粘贴和剪切子 shell 会变得很棘手。从命令行运行的代码在带入 shell 脚本时无法解析。至少这是我的经验(这促使我采用这种方法)。

于 2020-01-22T03:18:49.723 回答
0

扩展来自@Met 的答案,也使用 Perl:
如果输入和输出是 TAB 分隔的:

perl -F'\t' -lane 'print join "\t", @F[1, 0]' in_file

如果输入和输出以空格分隔:

perl -lane 'print join " ", @F[1, 0]' in_file

这里,
-e告诉 Perl 查找内联代码,而不是在单独的脚本文件中,
-n一次读取输入 1 行,在读取该行后
-l删除输入记录分隔符(\n在 *NIX 上)(类似于chomp),并添加输出每个记录分隔符(\n在 *NIX 上)print
-a将空格上的输入行拆分为数组@F
-F'\t'结合-a将 TAB 上的输入行拆分为数组,而不是空格@F

@F[1, 0]是由 array 的第二个和第一个元素组成的数组@F,按此顺序。请记住,Perl 中的数组是零索引的,而字段cut是 1 索引的。因此 中的字段与@F[0, 1]中的字段相同cut -f1,2

请注意,与上面发布的其他一些答案相比,这种表示法可以更灵活地操作输入(这对于简单的任务来说很好)。例如:

# reverses the order of fields:
perl -F'\t' -lane 'print join "\t", reverse @F' in_file

# prints last and first fields only:
perl -F'\t' -lane 'print join "\t", @F[-1, 0]' in_file
于 2020-07-31T20:40:36.960 回答