删除在线重复的命令行
Command line to remove in-line dupes
从一行中删除重复项的快速简洁方法是什么?
我有一个格式如下的文件:
alpha • a | b | c | a | b | c | d
beta • h | i | i | h | i | j | k
gamma • m | n | o
delta • p | p | q | r | s | q
所以第 1 列有一个词条,然后是用竖线分隔的各种单词,重复的数量无法预测。所需的输出已删除重复项,如:
alpha • a | b | c | d
beta • h | i | j | k
gamma • m | n | o
delta • p | q | r | s
我的输入文件有几千行。上面的希腊名称对应类别名称(例如,"baseball");并且字母表对应于英语词典中的单词(可能包含空格或重音符号),例如"ball game | batter | catcher | catcher | designated hitter"。
这可以通过多种方式进行编程,但我怀疑有一种聪明的方法可以做到这一点。我经常遇到这种情况的变体,想知道是否有一种简洁优雅的方法来做到这一点。我使用的是 MacOS,所以一些花哨的 unix 选项不可用。
额外的复杂性,我经常在最后有一个注释应该保留,例如,
zeta • x | y | x | z | z ; comment here
P.S。此输入实际上是先前 Whosebug 问题的输出:
Command line to match lines with matching first field (sed, awk, etc.)
BSD awk
没有 GNU awk
内置的 sort
函数,但我不确定它们是否必要。项目符号 • (U+2022) 对 awk
造成了一些影响。
我建议将项目符号预处理为单字节字符。我选择了 @
,但如果您愿意,您可以使用 Control-A 或其他名称。您的数据在文件 data
中。我注意到在 gamma
行的 m
之前有一个双 space;我假设这并不重要。
sed 's/•/@/' data |
awk -F ' *[@|] *' '
{
delete names
delete comments
delete fields;
if ($NF ~ / *;/) { split($NF, comments, / *; */); $NF=comments[1]; }
j = 1;
for (i = 2; i <= NF; i++)
{
if (names[$i]++ == 0)
fields[j++] = $i;
}
printf("%s", );
delim = "•"
for (k = 1; k < j; k++)
{
printf(" %s %s", delim, fields[k]);
delim = "|";
}
if (comments[2])
printf(" ; %s", comments[2]);
printf("\n");
}'
运行 这会产生:
alpha • a | b | c | d
beta • h | i | j | k
gamma • m | n | o
delta • p | q | r | s
zeta • x | y | z ; comment here
使用 bash、排序、xargs、sed:
while IFS='•;' read -r a b c; do
IFS="|" read -ra array <<< "$b"
array=( "${array[@]# }" )
array=( "${array[@]% }" )
readarray -t array < <(printf '%s[=10=]' "${array[@]}" | sort -zu | xargs -0n1)
SAVE_IFS="$IFS"; IFS="|"
s="$a• ${array[*]}"
[[ $c != "" ]] && s="$s ;$c"
sed 's/|/ | /g' <<< "$s"
IFS="$SAVE_IFS"
done < file
输出:
alpha • a | b | c | d
beta • h | i | j | k
gamma • m | n | o
delta • p | q | r | s
zeta • x | y | z ; comment here
我想 "m" 之前的两个空格是打错了。
这可能适合您 (GNU sed):
sed 'h;s/.*• \([^;]*\).*/cat <<\! | sort -u ||!/;s/\s*|\s*/\n/2ge;s/\n/ | /g;G;s/^\(.*\)\n\(.*• \)[^;]*//;s/;/ &/' file
这个想法的草图是:删除每行的头部和尾部,将数据变形为一个迷你文件,使用标准实用程序排序并删除重复项,然后将行重新组合在一起。
此处保留了该行的副本 space。删除了 ID 和评论。使用 cat 和 bash here-document 语法将数据压缩到一个文件中,并通过排序进行管道传输(如果您的排序没有配备 -u
选项,则使用 uniq )。评估模式 space 并通过将原始行附加到模式 space 并使用正则表达式模式匹配来重新组合行。
从一行中删除重复项的快速简洁方法是什么?
我有一个格式如下的文件:
alpha • a | b | c | a | b | c | d
beta • h | i | i | h | i | j | k
gamma • m | n | o
delta • p | p | q | r | s | q
所以第 1 列有一个词条,然后是用竖线分隔的各种单词,重复的数量无法预测。所需的输出已删除重复项,如:
alpha • a | b | c | d
beta • h | i | j | k
gamma • m | n | o
delta • p | q | r | s
我的输入文件有几千行。上面的希腊名称对应类别名称(例如,"baseball");并且字母表对应于英语词典中的单词(可能包含空格或重音符号),例如"ball game | batter | catcher | catcher | designated hitter"。
这可以通过多种方式进行编程,但我怀疑有一种聪明的方法可以做到这一点。我经常遇到这种情况的变体,想知道是否有一种简洁优雅的方法来做到这一点。我使用的是 MacOS,所以一些花哨的 unix 选项不可用。
额外的复杂性,我经常在最后有一个注释应该保留,例如,
zeta • x | y | x | z | z ; comment here
P.S。此输入实际上是先前 Whosebug 问题的输出: Command line to match lines with matching first field (sed, awk, etc.)
BSD awk
没有 GNU awk
内置的 sort
函数,但我不确定它们是否必要。项目符号 • (U+2022) 对 awk
造成了一些影响。
我建议将项目符号预处理为单字节字符。我选择了 @
,但如果您愿意,您可以使用 Control-A 或其他名称。您的数据在文件 data
中。我注意到在 gamma
行的 m
之前有一个双 space;我假设这并不重要。
sed 's/•/@/' data |
awk -F ' *[@|] *' '
{
delete names
delete comments
delete fields;
if ($NF ~ / *;/) { split($NF, comments, / *; */); $NF=comments[1]; }
j = 1;
for (i = 2; i <= NF; i++)
{
if (names[$i]++ == 0)
fields[j++] = $i;
}
printf("%s", );
delim = "•"
for (k = 1; k < j; k++)
{
printf(" %s %s", delim, fields[k]);
delim = "|";
}
if (comments[2])
printf(" ; %s", comments[2]);
printf("\n");
}'
运行 这会产生:
alpha • a | b | c | d
beta • h | i | j | k
gamma • m | n | o
delta • p | q | r | s
zeta • x | y | z ; comment here
使用 bash、排序、xargs、sed:
while IFS='•;' read -r a b c; do
IFS="|" read -ra array <<< "$b"
array=( "${array[@]# }" )
array=( "${array[@]% }" )
readarray -t array < <(printf '%s[=10=]' "${array[@]}" | sort -zu | xargs -0n1)
SAVE_IFS="$IFS"; IFS="|"
s="$a• ${array[*]}"
[[ $c != "" ]] && s="$s ;$c"
sed 's/|/ | /g' <<< "$s"
IFS="$SAVE_IFS"
done < file
输出:
alpha • a | b | c | d beta • h | i | j | k gamma • m | n | o delta • p | q | r | s zeta • x | y | z ; comment here
我想 "m" 之前的两个空格是打错了。
这可能适合您 (GNU sed):
sed 'h;s/.*• \([^;]*\).*/cat <<\! | sort -u ||!/;s/\s*|\s*/\n/2ge;s/\n/ | /g;G;s/^\(.*\)\n\(.*• \)[^;]*//;s/;/ &/' file
这个想法的草图是:删除每行的头部和尾部,将数据变形为一个迷你文件,使用标准实用程序排序并删除重复项,然后将行重新组合在一起。
此处保留了该行的副本 space。删除了 ID 和评论。使用 cat 和 bash here-document 语法将数据压缩到一个文件中,并通过排序进行管道传输(如果您的排序没有配备 -u
选项,则使用 uniq )。评估模式 space 并通过将原始行附加到模式 space 并使用正则表达式模式匹配来重新组合行。