【问题标题】:How do I recursively append a unique identifier to a series of identical strings in bash?如何递归地将唯一标识符附加到bash中的一系列相同字符串?
【发布时间】:2019-12-23 01:16:17
【问题描述】:

我有一个名为 test2.txt 的文件,内容如下:

string_9989 1.000 1.4567
string_9989 1.001001 1.2345
string_9989 1.1111111 2.22222222 3.33333333
string_9989 1.0000 1.4567 2.3456
string_1234 1.000000 1.3456
string_1234 1.3456 2.3456 3.5678
string_1234 1.234 3.456 5.678 6.789
string_1234 1.2 3.4 4.5 5.6

string_9989 的第一个实例与 string_1234 的第一个实例相关; string_9989 的第二个实例与 string_1234 的第二个实例相关,等等。我想提取每个字符串实例的行并将它们打印到新文件中(所以从 string_9989 和 string_1234 的第一个实例到 1 个文件的行,第二个实例每个到另一个文件等)。我的想法是将索引附加到字符串的每个实例并使用 grep 提取该实例并打印到新文件,但我无法成功创建索引。

我尝试了以下代码:

  • 将 string_[0-9] 的每个唯一实例保存到数组中
  • 循环遍历数组,使用 sed 将 _$j 添加到字符串的末尾
#!/bin/bash

mapfile -t string_array < <( grep -Eio "string_[0-9]*" test2.txt | sort -u )

for i in ${string_array[@]}; do
        count_path=$(grep -c $i test2.txt)
        j=0
        while [ $count_path -gt 0 ]; do
                sed -i "$j,/$i/{s/\<$i\>/&_$j/}" test2.txt
                let count_path=$(expr $count_path - 1)
                let j=j+1
        done
done

我期待这个输出:

string_9989_0 1.000 1.4567
string_9989_1 1.001001 1.2345
string_9989_2 1.1111111 2.22222222 3.33333333
string_9989_3 1.0000 1.4567 2.3456
string_1234_0 1.000000 1.3456
string_1234_1 1.3456 2.3456 3.5678
string_1234_2 1.234 3.456 5.678 6.789
string_1234_3 1.2 3.4 4.5 5.6

但我却得到了这个:

string_9989_0 1.000 1.4567
string_9989_1 1.001001 1.2345
string_9989_2 1.1111111 2.22222222 3.33333333
string_9989_3 1.0000 1.4567 2.3456
string_1234_0 1.000000 1.3456
string_1234 1.3456 2.3456 3.5678
string_1234 1.234 3.456 5.678 6.789
string_1234 1.2 3.4 4.5 5.6

为什么没有完成第二个字符串的追加?我正在使用 bash 版本 4.1.2(1)

【问题讨论】:

  • 我建议您专注于满足您实际需求的解决方案。否则,此问题是XY problem 的示例。
  • 我明白了。这是我的第一篇文章,所以....我提出了我的问题以及到目前为止我为实现目标所做的工作。如果您没有解决方案,您为什么要发帖?
  • I want to extract the line for each instance of the strings and print them to new files - 那你为什么要在字段中添加后缀?那么,你想提取它还是添加后缀?

标签: bash sed while-loop grep


【解决方案1】:

我想提取每个字符串实例的行并将它们打印到新文件(因此从 string_9989 和 string_1234 的第一个实例到 1 个文件的行,每个到另一个文件的第二个实例等)

求救:

awk '{ if (n != $1) { cnt=1; n=$1; }; print $0 > "file" cnt ".txt"; cnt=cnt+1; }'

以下脚本:

cat <<EOF |
string_9989 1.000 1.4567
string_9989 1.001001 1.2345
string_9989 1.1111111 2.22222222 3.33333333
string_9989 1.0000 1.4567 2.3456
string_1234 1.000000 1.3456
string_1234 1.3456 2.3456 3.5678
string_1234 1.234 3.456 5.678 6.789
string_1234 1.2 3.4 4.5 5.6
EOF
awk '{ if (n != $1) { cnt=1; n=$1; }; print $0 > "file" cnt ".txt"; cnt=cnt+1; }'

find
tail -n+1 *

将生成以下输出:

./file4.txt
./file1.txt
./file3.txt
./file2.txt
==> file1.txt <==
string_9989 1.000 1.4567
string_1234 1.000000 1.3456

==> file2.txt <==
string_9989 1.001001 1.2345
string_1234 1.3456 2.3456 3.5678

==> file3.txt <==
string_9989 1.1111111 2.22222222 3.33333333
string_1234 1.234 3.456 5.678 6.789

==> file4.txt <==
string_9989 1.0000 1.4567 2.3456
string_1234 1.2 3.4 4.5 5.6
  • 首先我们检查实例是否与最后一行不同
  • 如果不同,我们将 cnt 重置为 1 并记住当前实例
  • 然后我们printf $0将整行打印到"file" cnt ".txt"文件名中
  • 之后,我们增加计数。
  • 输入文件必须按第一列排序。

模仿 awk 的 bash 解决方案如下所示:

while IFS=' ' read -r instance rest; do
  if [ "${last_instance:-}" != "$instance" ]; then
    cnt=1
    last_instance=$instance
  fi
  printf "%s %s\n" "$instance" "$rest" >> "file${cnt}.txt"
  cnt=$((cnt + 1))
done

【讨论】:

  • 这可以在几秒钟内使用几百万条记录来处理我的实际文件。肯定会越来越熟悉awk。谢谢!
【解决方案2】:

您对依赖标准 unix 字符串处理工具(如 grep 和 sed)的直觉很好,如果您进一步采用该想法并添加粘贴和拆分以及

a=`grep string_9989 test2.txt`
b=`grep string_1234 test2.txt`
both=`paste <(echo "$a") <(echo "$b")` # paste them side-by-side
echo "$both" | split -l1 # split into n 1-line files

【讨论】:

    【解决方案3】:

    这可能对你有用(GNU grep、粘贴和拆分):

    paste -d'\n' <(grep 9989 file) <(grep 1234 file) | split -dl2 - file
    

    使用两次调用 grep 将 file 拆分为两部分,并使用 paste 交错文件。

    将生成的文件通过管道拆分,生成的文件将命名为file00, file01, file02 etc

    【讨论】:

      【解决方案4】:

      解决您的实际问题I want to extract the line for each instance of the strings and print them to new files

      使用 GNU awk:

      awk '{print > "out" ++cnt[$1]}' file
      

      使用任何 awk 并假设您的真实输入与您的示例一样排序:

      awk '$1!=prev{prev=$1; close(out); out="out" ++cnt} {print > out}' file
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-12-28
        • 2013-11-28
        • 1970-01-01
        • 2014-10-28
        • 2011-01-04
        • 2012-05-27
        • 2018-02-28
        • 1970-01-01
        相关资源
        最近更新 更多