我有一个文件,格式如下:
Column1 Column2 str1 1 str2 2 str3 3
我想要重新排列这些列。我尝试了以下命令:
cut -f2,1 file.txt
该命令无法重新排列列。有任何想法为什么它不起作用吗?
针对cut(1)
命令手册:
使用-b、-c或-f参数中的一个且仅一个。每个列表由一个范围或多个由逗号分隔的范围组成。所选输入按照读取顺序写入,并且仅写入一次。
它首先到达字段1,因此打印字段1,然后是字段2。
请改用awk
:
awk '{ print $2 " " $1}' file.txt
您也可以组合使用剪切
和粘贴
:
paste <(cut -f2 file.txt) <(cut -f1 file.txt)
通过评论:可以避免使用进程替换并通过以下方式去除一个 cut 实例:
paste file.txt file.txt | cut -f2,3
cut
对于可变长度的列可以正常工作。 - tripleeebash
isms 并通过以下方式删除一个 cut
实例:paste file.txt file.txt | cut -f2,3
- agcjoin
:join -t $'\t' -o 1.2,1.1 file.txt file.txt
注意事项:
-t $'\t'
In GNU join
the more intuitive -t '\t'
without the $
fails, (coreutils v8.28 and earlier?); it's probably a bug that a workaround like $
should be necessary. See: unix join separator char.
Even though there's just one file being worked on, join
syntax requires two filenames. Repeating the file name allows join
to perform the desired action.
For systems with low resources join
offers a smaller footprint than some of the tools used in other answers:
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
仅使用shell,
while read -r col1 col2
do
echo $col2 $col1
done <"file"
"$col2"
和"$col1"
- 数据中可能有shell元字符或其他诡计。 - tripleeeperl -ane 'print "$F[1] $F[0]\n"' < file.txt
运行Perl的优点是(如果您了解Perl),您可以对F进行更多计算,而不仅仅是重新排列列。
perl -ae print
的作用就像cat
一样。 - pwes我曾经做过类似的工作,虽然不是专家,但我想分享一下我所使用的命令。我有一个多列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
诚然,这可能有些粗糙,但可以进行微调以适应需要!
cut
。对于复制,paste
等只适用于文件,而不适用于流。在这种情况下,请改用sed
。
cat file.txt | sed s/'.*'/'&\t&'/ | cut -f2,3
这适用于文件和流,并且如果您不仅仅使用cat
从文件中读取内容,而是在重新排列列之前执行了一些有趣的操作,则会更加有趣。
相比之下,以下内容不起作用:
cat file.txt | paste - - | cut -f2,3
在这里,双stdin占位符paste
不会复制stdin,而是读取下一行。
使用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
是要匹配的列。
使用paste
和cut
子shell在实现shell脚本内部时会变得棘手。在命令行中可行的代码,在带入shell脚本时无法解析。至少这是我的经验(这也驱使我采用这种方法)。
扩展@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
逐行读取输入,-l
在读取行后删除输入记录分隔符(在 *NIX 上为 \n
)(类似于 chomp
),并将输出记录分隔符(在 *NIX 上为 \n
)添加到每个 print
中,-a
将输入行按空格拆分成数组 @F
,-F'\t'
与 -a
结合使用,将输入行拆分为 TAB,而不是空格,进入数组 @F
。
@F[1, 0]
是由数组 @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
awk
的-FS
和-OFS
选项来使用自定义输入和输出字段分隔符(就像cut
的-d
和--output-delimiter
一样)。 - malanaFS
是一个选项,OFS
则是一个变量。例如:awk -v OFS=";" -F"\t" '{print $2,$1}'
- malanaawk
之前通过| sed 's/\r//' |
进行管道传输。 - jakub.gawk '{print $4 "\t" $2 "\t" $6 "\t" $7}' file
- FatihSarigolcut
无法重新排序。手册中应该只说明这一点。 - CervEd