【问题标题】:bash: looping over the files with extra conditionsbash:使用额外条件循环文件
【发布时间】:2019-08-26 17:01:59
【问题描述】:

在工作目录中,有几个文件根据文件名的结尾后缀分为几组。以下是 4 个组的示例:

# group 1 has 5 files
NpXynWT_apo_300K_1.pdb
NpXynWT_apo_300K_2.pdb
NpXynWT_apo_300K_3.pdb
NpXynWT_apo_300K_4.pdb
NpXynWT_apo_300K_5.pdb
# group 2 has two files
NpXynWT_apo_340K_1.pdb
NpXynWT_apo_340K_2.pdb
# group 3 has 4 files
NpXynWT_com_300K_1.pdb
NpXynWT_com_300K_2.pdb
NpXynWT_com_300K_3.pdb
NpXynWT_com_300K_4.pdb
# group 4 has 1 file
NpXynWT_com_340K_1.pdb

我已经写了一个简单的 bash 工作流来

  1. 列表项通过 SED 对每个填充物进行预处理:在每个文件中添加一些内容
  2. cat 将属于属于同一组的预处理文件放在一起

这是我实现工作流的脚本,其中我创建了一个包含组名称的数组,并根据从 1 到 5 的文件索引循环它

# list of 4 groups
systems=(NpXynWT_apo_300K NpXynWT_apo_340K NpXynWT_com_300K NpXynWT_com_340K)

 # loop over the groups
for model in "${systems[@]}"; do  
    # loop over the files inside of each group
    for i in {0001..0005}; do
    # edit file via SED
    sed -i "1 i\This is $i file of the group" "${pdbs}"/"${model}"_"$i"_FA.pdb
    done
# after editing cat the pre-processed filles
  cat "${pdbs}"/"${model}"_[1-5]_FA.pdb > "${output}/${model}.pdb"
done

改进此脚本的问题: 1) 如何在内部 (while) 循环中添加一些检查条件(例如通过 IF 语句)以考虑仅现有文件?在我的示例中,脚本始终根据其中一个组中的最大数量(这里是第一组中的 5 个文件)循环 5 个文件(每个组)

for i in {0001..0005}; do

我宁愿循环给定组的所有现有文件,并在文件不存在的情况下中断 while 循环(例如,考虑只有 1 个文件的第 4 组)。这是示例,但是无法正常工作

 # loop over the groups with the checking of the presence of the file
for model in "${systems[@]}"; do  
    i="0"
    # loop over the files inside of each group
    for i in {0001..9999}; do
    if [ ! -f "${pdbs}/${model}_00${i}_FA.pdb" ]; then
echo 'File '${pdbs}/${model}_00${i}_FA.pdb' does not exits!'
    break
    else
    # edit file via SED
    sed -i "1 i\This is $i file of the group" "${pdbs}"/"${model}"_00"$i"_FA.pdb
    i=$[$i+1]
    fi
    done
done

是否可以从组中循环任意数量的现有填充(而不是仅仅限制给定的例如非常大数量的文件

for i in {0001..9999}; do?

【问题讨论】:

    标签: arrays bash loops conditional-statements


    【解决方案1】:
    1. 您可以使用-f 测试检查文件是否存在,如果不存在则break

      if [ ! -f "${pdbs}/${model}_${i}_FA.pdb" ]; then
         break
      fi
      
    2. 您现有的cat 命令已经只计算每个组中的现有文件,因为"${pdbs}"/"${model}"_[1-5]_FA.pdb bash 在这里执行文件名扩展,而不是简单地将[1-5] 扩展为所有可能的值。您可以在以下示例中看到这一点:

      > touch f1 f2 f5   # files f3 and f4 do not exist
      > echo f[1-5]
      f1 f2 f5
      

      请注意,f[1-5] 没有扩展为 f1 f2 f3 f4 f5

    更新

    如果您希望 glob 表达式匹配以大于 9 的数字结尾的文件,[1-n] 语法将不起作用。原因是[...] 语法定义了匹配单个字符的模式。例如,表达式foo[1-9] 将匹配文件foo1foo9,但不匹配foo10foo99

    foo[1-99] 这样的操作是行不通的,因为它并不意味着你可能认为它意味着什么。 [] 的内部可以包含任意数量的单个字符或字符范围。例如,[1-9a-nxyz] 将匹配从 '1''9'、从 'a''n' 的任何字符,或任何字符 'x''y''z',但它会 not 匹配 '0''q''r' 等。或者就此而言,它也不匹配任何大写字母。

    所以[1-99] 不被解释为从 1 到 99 的 数字 的范围,它被解释为由 '1' 的范围组成的 字符 的集合到“9”,加上单个字符“9”。因此模式[1-9][1-99] 是等价的,并且只会匹配字符'1''9'。后一个表达式中的第二个9 是多余的。

    但是,您仍然可以通过扩展 glob 实现您想要的,您可以使用命令 shopt -s extglob 启用:

    > touch f1 f2 f5 f99 f100000 f129828523
    > echo f[1-99999999999]       # Doesn't work like you want it to
    f1 f2 f5
    > shopt -s extglob
    > echo f+([0-9])
    f1 f2 f5 f99 f100000 f129828523
    

    +([0-9]) 表达式是一个扩展的 glob 表达式,由两部分组成:[0-9],其含义在这一点上应该很明显,以及封闭的+(...)

    +(pattern) 语法是一个extglob 表达式,表示匹配pattern 的一个或多个实例。在本例中,我们的模式是 [0-9],因此 extglob 表达式 +([0-9]) 匹配任何 0-9 的数字字符串。

    但是,您应该注意,这意味着它也匹配 000000000 之类的内容。如果您只对大于或等于 1 的数字感兴趣,那么您应该这样做(启用extglob):

    > echo f[1-9]*([0-9])
    

    请注意此处的 *(pattern) 而不是 +(pattern)* 表示匹配 或更多模式实例。这是我们想要的,因为我们已经将第一个数字与[1-9] 匹配。例如,f[1-9]+([0-9]) 与文件名 f1 不匹配。

    您可能不想在整个脚本中启用extglob,特别是如果您在脚本的其他地方有任何可能意外被解释为extglob 表达式的正则glob 表达式。要在完成后禁用extglob,请执行以下操作:

    shopt -u extglob
    

    这里还有另一件重要的事情需要注意。如果 glob 模式与 any 文件不匹配,则将其解释为原始字符串,并且保持不变。

    例如:

    > echo This_file_totally_does_not_exist*
    This_file_totally_does_not_exist*
    

    在您的情况下或更重要的是,假设在您的第 4 种情况下有零个文件,例如没有包含NpXynWT_com_340K 的文件。在这种情况下,如果您尝试使用包含 NpXynWT_com_340K 的 glob,则会将整个 glob 作为文字字符串:

    > shopt -s extglob
    > echo NpXynWT_com_340K_[1-9]*([0-9])
    echo NpXynWT_com_340K_[1-9]*([0-9])
    

    这显然不是您想要的,尤其是在您尝试 cat 匹配文件的脚本中间。幸运的是,您可以设置另一个选项来使不匹配的 glob 扩展为空:

    > shopt -s nullglob
    > echo This_file_totally_does_not_exist*   # prints nothing
    

    extglob 一样,如果您将nullglob 保持打开状态,您的脚本中的其他地方可能会出现意外行为。

    【讨论】:

    • 非常感谢!所以对于第二个问题,我可以简单地为任意数量的文件指出 cat "${pdbs}"/"${model}"_[1-999999]_FA.pdb ? “无穷大”而不是 99999 是否有一些价值? :-)
    • @user3470313 实际上,我弄错了 bash 如何扩展 [1-999999]。它不会起作用。 [x-y] 语法是只匹配单个字符的模式匹配表达式。所以[1-9] 是您尝试匹配数字 1-N 时所能达到的极限。 [1-999999] 的实际含义是字符 '1' 到 '9' 的集合,加上字符 '9'、'9'、'9'、'9' 或 '9'。例如,您可以使用[1-9abc],它会匹配以下任何字符{ '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c' }。但是foo[1-99] 确实 匹配文件名foo99
    • @user3470313 查看我上面的更新,了解如何形成一个扩展的 glob,它可以匹配从 1 到无穷大的任何数字。
    • 再次感谢迈克!如果具有给定索引的文件不存在,当我需要引入 IF 语句来中断 while 循环时,关于我任务第一部分的唯一问题。实际上,我尝试使用我的脚本来执行此操作(请参阅我的第一篇文章已更正),但带有条件的循环无法正常工作。怎么可能修复它?再次提前感谢!
    • 为什么在固定代码的${i}前面加了00
    猜你喜欢
    • 1970-01-01
    • 2020-11-03
    • 1970-01-01
    • 2014-03-30
    • 1970-01-01
    • 2014-10-30
    • 2017-06-05
    • 1970-01-01
    • 2023-02-13
    相关资源
    最近更新 更多