我想确认一下下面的工作脚本是否有一种更有效的方法,它将我的tsv文件中的列数据分割开来。我怀疑这可以用awk或sed来实现,但我的经验有限,我无法在搜索中使用正确的单词,找出某人在哪里取得了类似的结果。如果有人能确认在某个地方是否有类似的帖子,或者教科书中有一章能给出答案,我会很感激你的指导。
在我的tsv数据集中,我有第6-9列,它们有一个ID值和匹配字符串的组合。我的目标是分离is和字符串,并将其余的列移到右边,以便为创建的其他列腾出空间。如果没有字符串数据,则复制ID值。如果数据为空白,则保留空白单元格。
client geo 2022-08-30 username city supplier1 10413 232080743 ham
client geo 2022-08-30 username city supplier2 (3219dds135) campaign (4049) 275975960 ham
client geo 2022-08-30 username city Supplier2 (3211239135) Campaign. (9591) 276220665 ham
client geo 2022-08-30 username city supplier3 (3219132DSAD35) Campaign(campaign) (72dad59) 232074226 ham
client geo 2022-08-30 username city Supplier4 (3242319135) campaign (CAMPAIGN) (7424) 232074240 ham
client geo 2022-08-30 username city Supplier4 (3242319135) Campaign - (CAMPAIGN) (7424) 232074240 ham
client geo 2022-08-30 username city 232074240 Campaign – (CAMPAI4324GN) (7424) DELIVERY (DSAD32E!) (232074240) ham
client geo 2022-08-30 username city ham#!/bin/bash
FILENAME="data"
cut -f 6 $FILENAME.tsv | sed -Ee 's/^.+ \(//g' -e 's/\)$//g' > $FILENAME-id.tsv
cut -f 6 $FILENAME.tsv | sed -Ee 's/ \(\S+\)$//g' > $FILENAME-name.tsv
cut -f 1 $FILENAME-id.tsv | paste $FILENAME-name.tsv - > $FILENAME-nameid.tsv
cut -f 7 $FILENAME.tsv | sed -Ee 's/^.+ \(//g' -e 's/\)$//g' > $FILENAME-id2.tsv
cut -f 7 $FILENAME.tsv | sed -Ee 's/ \(\S+\)$//g' > $FILENAME-name2.tsv
cut -f 1 $FILENAME-id2.tsv | paste $FILENAME-name2.tsv - > $FILENAME-nameid2.tsv
cut -f 8 $FILENAME.tsv | sed -Ee 's/^.+ \(//g' -e 's/\)$//g' > $FILENAME-id3.tsv
cut -f 8 $FILENAME.tsv | sed -Ee 's/ \(\S+\)$//g' > $FILENAME-name3.tsv
cut -f 1 $FILENAME-id3.tsv | paste $FILENAME-name3.tsv - > $FILENAME-nameid3.tsv
cut -f 1,2 $FILENAME-nameid.tsv | paste $FILENAME.tsv - > 1-$FILENAME.tsv
cut -f 1,2 $FILENAME-nameid2.tsv | paste 1-$FILENAME.tsv - > 2-$FILENAME.tsv
cut -f 1,2 $FILENAME-nameid3.tsv | paste 2-$FILENAME.tsv - > 3-$FILENAME.tsv
awk -F'\t' -v OFS="\t" '{print $1,$2,$3,$4,$5,$10,$11,$12,$13,$14,$15,$9}' 3-$FILENAME.tsv > 4-$FILENAME.tsvclient geo 2022-08-30 username city supplier1 supplier1 10413 10413 232080743 232080743 ham
client geo 2022-08-30 username city supplier2 3219dds135 campaign 4049 275975960 275975960 ham
client geo 2022-08-30 username city Supplier2 3211239135 Campaign. 9591 276220665 276220665 ham
client geo 2022-08-30 username city supplier3 3219132DSAD35 Campaign(campaign) 72dad59 232074226 232074226 ham
client geo 2022-08-30 username city Supplier4 3242319135 campaign (CAMPAIGN) 7424 232074240 232074240 ham
client geo 2022-08-30 username city Supplier4 3242319135 Campaign - (CAMPAIGN) 7424 232074240 232074240 ham
client geo 2022-08-30 username city 232074240 232074240 Campaign – (CAMPAI4324GN) 7424 DELIVERY (DSAD32E!) 232074240 ham
client geo 2022-08-30 username city ham发布于 2022-08-31 19:51:06
在Bash中实现这一相对简单的任务并不容易。它在Python中是很简单的,在现代系统中也是如此,我认为这是值得的。
从列中提取id和name值的sed操作并不简单,而且是重复的。为了避免重复,您可以定义一个函数,并使用一个描述性名称来指示读者正在发生的事情,例如:
extract_id() {
sed -Ee 's/^.+ \(//g' -e 's/\)$//g'
}
extract_name() {
sed -Ee 's/ \(\S+\)$//g'
}
cut -f 6 "$FILENAME.tsv" | extract_id > "$FILENAME-id.tsv"
cut -f 6 "$FILENAME.tsv" | extract_name > "$FILENAME-name.tsv"我还在包含变量的参数周围添加了推荐的双引号,因为这是一个很好的实践。
然后你可以更进一步。与其对3个不同的列重复相同的操作,不如考虑使用一个循环:
for col in 6 7 8; do
cut -f "$col" "$FILENAME.tsv" | extract_id > "$FILENAME-$col-id.tsv"
cut -f "$col" "$FILENAME.tsv" | extract_name > "$FILENAME-$col-name.tsv"
done的确,上面对输出文件名的更改并不难适应脚本的其余部分,这样就更容易看出这些文件来自何处。
实际上,许多cut命令都是不必要的,从带有单个列的文件中获取第一列,或者从具有两列的文件中获取前2列:
剪切-f $1 $FILENAME-id.tsv粘贴$FILENAME->$FILENAME-> $FILENAME-nameid.tsv剪切-f 1,2美元文件名-nameid.tsv\ cut $FILENAME.tsv -> 1-$FILENAME.tsv
如果没有cut,这些代码可以编写得更简单:
paste "$FILENAME-name.tsv" "$FILENAME-id.tsv" > "$FILENAME-nameid.tsv"
paste "$FILENAME.tsv "$FILENAME-nameid.tsv" > "1-$FILENAME.tsv"此外,许多剪切+粘贴命令都会创建不必要的中间文件。也就是说,一旦您在单独的文件中拥有id和name列,您就不需要创建具有name-id对的文件,您可以直接将它们粘贴到一起。
在我们前面使用的循环之后,可以使用一个paste跳过大多数中间文件:
paste "$FILENAME.tsv" "$FILENAME"-{6,7,8}-{name,id}.tsv > "3-$FILENAME.tsv"还可以通过管道直接进入awk消除最后一个中间文件:
paste "$FILENAME.tsv" "$FILENAME"-{6,7,8}-{name,id}.tsv |
awk -F'\t' -v OFS="\t" '{print $1,$2,$3,$4,$5,$10,$11,$12,$13,$14,$15,$9}' > "4-$FILENAME.tsv"实际上,与其键入awk中的所有列,不如为第9列引入一个中间文件,然后将其余的文件粘贴到一起,这样就更简单了:
cut -f9 "$FILENAME.tsv" > "$FILENAME-9.tsv"
cut -f1-5 "$FILENAME.tsv" |
paste - "$FILENAME"-{6,7,8}-{name,id}.tsv "$FILENAME-9.tsv" > "4-$FILENAME.tsv"我不确定您是否需要脚本创建的所有中间文件。对于以后不需要的任何文件,您可以使用临时目录,并在退出时使脚本清理:
workdir=$(mktemp -d)
cleanup() {
find "$workdir"
rm -fr "$workdir"
}
trap cleanup EXITSHOUT_CASE通常用于系统环境变量。看到它们被用于其他目的是令人困惑的,并可能导致问题。
有了上述建议,脚本就会减少重复,使用更少的中间文件,并进行简单的清理:
#!/bin/bash
set -euo pipefail
input="data.txt"
output="4-$input.tsv"
workdir=$(mktemp -d)
cleanup() {
rm -fr "$workdir"
}
trap cleanup EXIT
extract_id() {
sed -Ee 's/^.+ \(//g' -e 's/\)$//g'
}
extract_name() {
sed -Ee 's/ \(\S+\)$//g'
}
for col in 6 7 8; do
cut -f "$col" "$input.tsv" | extract_name > "$workdir/$col-name.tsv"
cut -f "$col" "$input.tsv" | extract_id > "$workdir/$col-id.tsv"
done
cut -f 9 "$input.tsv" > "$workdir/9.tsv"
cut -f 1-5 "$input.tsv" |
paste - "$workdir"/{6,7,8}-{name,id}.tsv "$workdir/9.tsv" > "$output"这仍然不是很好,因为它仍然读取输入文件8次。当然,在一次传递中使用awk就可以做到这一点,但现代语言Python也是如此。
https://codereview.stackexchange.com/questions/279328
复制相似问题