【问题标题】:read multiple files in bash在bash中读取多个文件
【发布时间】:2015-04-03 19:12:51
【问题描述】:

我有两个 .txt 文件,我想在 .sh 脚本中每行同时读取它们。两个 .txt 文件的行数相同。在循环内部,我想使用 sed 命令更改另一个文件中的 full_sample_name 和 sample_name。 如果您只读取一个文件,我知道这是如何工作的,但我无法让它适用于两个文件。

#! /bin/bash

FULL_SAMPLE="file1.txt"
SAMPLE="file2.txt"

while read ... && ...
do
    sed -e "s/\<full_sample_name\>/$FULL_SAMPLE/g" -e "s/\<sample_name\>/$SAMPLE/g" pipeline.sh > $SAMPLE.sh

done < ...?

【问题讨论】:

  • FOO = bar 不是 bash 中的变量赋值;必须是FOO=bar;空格很重要。
  • 在这里查看我的答案...stackoverflow.com/questions/22730675/…
  • 你是对的,我的错。改了。
  • (另外,全大写变量名保留给 shell 内置函数和环境变量;参见 POSIX 规范的相关部分pubs.opengroup.org/onlinepubs/009695399/basedefs/…,第四段以“环境变量名”开头——用户应用程序在shell 脚本 [在环境变量和常规 shell 变量之间共享命名空间] 应使用至少包含一个小写字符的变量名以避免冲突。
  • @CharlesDuffy 更新版本 (pubs.opengroup.org/onlinepubs/9699919799/basedefs/…) 没有这么说。它说:“包含小写字母的环境变量名称的名称空间是为应用程序保留的。应用程序可以使用来自该名称空间的名称定义任何环境变量,而无需修改标准实用程序的行为。”这显然不一样。

标签: bash sed


【解决方案1】:

Charles 提供了一个非常好的答案。

您可以使用paste 将文件的行与一些分隔符(不应出现在文件中)连接起来:

paste -d ":" file1.txt file2.txt | while IFS=":" read -r full samp; do
    do_stuff_with "$full" and "$samp"
done

【讨论】:

  • 如果您想确保处理 所有 行,即使输入文件的行数不同(paste 处理“已用完行的输入文件” "就像一个无穷无尽的空字符串源)。如果你使用 Bash,你可以很容易地选择一个在一般文本文件中不太可能找到的分隔符,使用 ANSI C-quoted string,例如 $'\3'(ASCII 值 3,END OF TEXT 控制字符):paste -d $'\3' ...while IFS=$'\3' ...
【解决方案2】:
#!/bin/bash

full_sample_file="file1.txt"
sample_file="file2.txt"

while read -r -u 3 full_sample_name && read -r -u 4 sample_name; do
    sed -e "s/\<full_sample_name\>/$full_sample_name/g" \
        -e "s/\<sample_name\>/$sample_name/g" \
        pipeline.sh >"$sample_name.sh"
done 3<"$full_sample_file" 4<"$sample_file" # automatically closed on loop exit

在这种情况下,我将文件描述符 3 分配给 file1.txt,将文件描述符 4 分配给 file2.txt。


顺便说一句,使用 bash 4.1 或更高版本,您不再需要手动处理文件描述符:

# opening explicitly, since even if opened on the loop, these need
# to be explicitly closed.
exec {full_sample_fd}<file1.txt
exec {sample_fd}<file2.txt

while read -r -u "$full_sample_fd" full_sample_name \
   && read -r -u "$sample_fd" sample_name; do
  : do stuff here with "$full_sample_name" and "$sample_name"
done

# close the files explicitly
exec {full_sample_fd}>&- {sample_fd}>&-

另一个注意事项:如果您的输入文件包含没有文字 NUL [作为 shell 脚本,它不应该],并且如果箭头括号是文字而不是字边界正则表达式字符),根本不使用 sed,而只是读取输入被转换成一个shell变量,并在那里做替换!

exec {full_sample_fd}<file1.txt
exec {sample_fd}<file2.txt
IFS= read -r -d '' input_file <pipeline.sh

while read -r -u "$full_sample_fd" full_sample_name \
   && read -r -u "$sample_fd" sample_name; do
  output=${input_file//'<full_sample_name>'/${full_sample_name}}
  output=${output//'<sample_name>'/${sample_name}}
  printf '%s' "$output" >"${sample_name}.sh"
done

# close the files explicitly
exec {full_sample_fd}>&- {sample_fd}>&-

【讨论】:

    【解决方案3】:

    使用 GNU Parallel,它看起来像这样:

    #! /bin/bash
    
    do_sed() {
        sed -e "s/\<full_sample_name\>/$1/g" -e "s/\<sample_name\>/$2/g" pipeline.sh > "$2".sh
    }
    export -f do_sed   
    
    parallel --xapply do_sed {1} {2} :::: file1.txt file2.txt
    

    额外的好处是您可以让它并行运行。根据您的存储系统,这可能会加快处理速度:在 raid6 上,我通过并行运行 10 个作业看到了 6 倍的加速。 YMMV,所以唯一确定的方法就是测试和测量。

    GNU Parallel 是一种通用的并行化器,可以轻松地在同一台机器上或在您可以通过 ssh 访问的多台机器上并行运行作业。

    如果您想在 4 个 CPU 上运行 32 个不同的作业,那么并行化的直接方法是在每个 CPU 上运行 8 个作业:

    GNU Parallel 会在完成后生成一个新进程 - 保持 CPU 处于活动状态,从而节省时间:

    安装

    如果没有为您的发行版打包 GNU Parallel,您可以进行个人安装,这不需要 root 访问权限。这样做可以在 10 秒内完成:

    (wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash
    

    有关其他安装选项,请参阅http://git.savannah.gnu.org/cgit/parallel.git/tree/README

    了解详情

    查看更多示例:http://www.gnu.org/software/parallel/man.html

    观看介绍视频:https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

    浏览教程:http://www.gnu.org/software/parallel/parallel_tutorial.html

    注册电子邮件列表以获得支持:https://lists.gnu.org/mailman/listinfo/parallel

    【讨论】:

      猜你喜欢
      • 2019-12-16
      • 2017-08-26
      • 1970-01-01
      • 2012-09-28
      • 1970-01-01
      • 2021-04-16
      • 1970-01-01
      • 2012-03-27
      • 2022-01-05
      相关资源
      最近更新 更多