我有以下格式的文件
列 1 列 2 str1 1 str2 2 str3 3
我希望重新排列列。我试过下面的命令
剪切 -f2,1 文件.txt
该命令不会对列重新排序。知道为什么它不起作用吗?
对于cut(1)
手册页:
使用 -b、-c 或 -f 之一,并且只能使用其中之一。每个 LIST 由一个范围组成,或者由逗号分隔的多个范围组成。所选输入的写入顺序与读取顺序相同,并且只写入一次。
它首先到达字段 1,因此被打印,然后是字段 2。
改用awk
:
awk '{ print $2 " " $1}' file.txt
你也可以结合cut
和paste
:
paste <(cut -f2 file.txt) <(cut -f1 file.txt)
通过评论:可以通过以下方式避免 bashisms 并删除一个 cut 实例:
paste file.txt file.txt | cut -f2,3
您可以为此使用 Perl:
perl -ane 'print "$F[1] $F[0]\n"' < file.txt
运行 perl 的优点是(如果您了解 Perl)您可以在 F 上进行比重新排列列更多的计算。
仅使用外壳,
while read -r col1 col2
do
echo $col2 $col1
done <"file"
使用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
只是在做一些非常相似的事情,我不是专家,但我想我会分享我使用过的命令。我有一个多列 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
在这里,双标准输入占位符paste
不复制标准输入,而是读取下一行。
使用 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 脚本时无法解析。至少这是我的经验(这促使我采用这种方法)。
扩展来自@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