【问题标题】:bash for loop with numerated names带有数字名称的 bash for 循环
【发布时间】:2013-02-17 16:20:31
【问题描述】:

我目前正在做一个数学项目,但在使用 bash 编程时遇到了一些障碍。

目前我有一个包含 800 个文本文件的目录,我想要做的是运行一个循环,将前 80 个文件(_01 到 _80)cat 放入一个新文件并保存在其他地方,然后是接下来的 80 个(_81 到 _160)个文件等等。

目录中的所有文件如下所示:ath_01、ath_02、ath_03 等。

谁能帮忙?

到目前为止我有:

#!/bin/bash

for file in /dir/*

do
echo ${file}
done

这只是简单地列出了我的文件。我知道我需要以某种方式使用 cat file1 file2 > newfile.txt,但这让我与 _01、_02 等的数字扩展名混淆了。

如果我将文件名更改为使用下划线以外的名称会有帮助吗?喜欢ath.01等?

干杯,

【问题讨论】:

    标签: macos bash for-loop batch-processing cat


    【解决方案1】:

    由于您提前知道您有多少文件以及它们的编号方式,因此“展开循环”可能更容易,可以这么说,并使用复制粘贴和一些手动调整来编写一个使用大括号扩展的脚本。

    #!/bin/bash
    
    cat ath_{001..080} > file1.txt
    cat ath_{081..160} > file2.txt
    cat ath_{161..240} > file3.txt
    cat ath_{241..320} > file4.txt
    cat ath_{321..400} > file5.txt
    cat ath_{401..480} > file6.txt
    cat ath_{481..560} > file7.txt
    cat ath_{561..640} > file8.txt
    cat ath_{641..720} > file9.txt
    cat ath_{721..800} > file10.txt
    

    否则,使用嵌套的 for 循环和 seq 命令

    N=800
    B=80
    for n in $( seq 1 $B $N ); do
        for i in $( seq $n $((n+B - 1)) ); do
           cat ath_$i
        done > file$((n/B + 1)).txt
    done
    

    外循环将迭代n 到 1、81、161 等。内循环将迭代 i 从 1 到 80,然后从 81 到 160 等。内循环的主体只是转储内容如果 ith 文件到标准输出,但循环的聚合输出存储在文件 1 中,然后存储在文件 2 中,等等

    【讨论】:

    • +1 不错的方法。如果您不喜欢更改,请查看更改并还原。
    • 谢谢,太好了!欣赏!
    • 您不能删除内部循环并使用 eval 将代码扩展为您最初的硬编码解决方案吗?然后每个输出文件只调用一次 Cat。
    • eval 存在安全风险,我将其视为无法以任何其他方式完成的任务的最后手段。
    【解决方案2】:

    你可以试试这样的:

    cat "$file" >> "concat_$(( ${file#/dir/ath_} / 80 ))"
    
    • 使用${file#/dir/ath_},您可以从文件名中删除前缀/dir/ath_
    • $(( / 80 ))你得到后缀除以80(整数除法)

    同样将循环改为

    for file in /dir/ath_*
    

    所以你只得到你需要的文件

    【讨论】:

    • 有趣!如果我理解正确,您对每个输入文件运行一次cat;您通过将输入文件编号除以 80 来选择目标文件,因此目标文件每 80 个文件更改一次。
    • @JonathanLeffler 没错。但是这个答案只有在文件按顺序编号时才有效。
    • 这取决于非顺序编号文件的“作品”定义。您的将 1..80 个文件组合在一起,纯粹基于文件名中嵌入的数字;我的替代方案将 1..80 个文件组合在一起,无论数字序列中的间隙如何。在问题没有明确方向的情况下,它们是“等效但不同的”。
    • 啊,我没想到那样运行它。干杯!
    【解决方案3】:

    如果您需要 80 个文件为一组,最好确保名称是可排序的;这就是为什么经常使用前导零的原因。假设文件名中只有一个下划线,名称中没有换行符,那么:

    SOURCE="/path/to/dir"
    TARGET="/path/to/other/directory"
    (
    cd $SOURCE || exit 1
    ls |
    sort -t _ -k2,2n |
    awk -v target="$TARGET" \
        '{ file[n++] = $1
           if (n >= 80)
           {
               printf "cat"
               for (i = 0; i < 80; i++)
                   printf(" %s", file[i]
               printf(" >%s/%s.%.2d\n", target, "newfile", ++number)
               n = 0
           }
         END {
           if (n > 0)
           {
               printf "cat"
               for (i = 0; i < n; i++)
                   printf(" %s", file[i]
               printf(" >%s/%s.%.2d\n", target, "newfile", ++number)
           }
         }' |
    sh -x
    )
    

    指定了两个目录(文件在哪里以及摘要应该放在哪里);该命令将目录更改为源目录(800 个文件所在的位置)。它列出了名称(如果需要,您可以指定一个 glob 模式)并按数字对它们进行排序。输出被馈送到awk,它会即时生成一个shell脚本。它一次收集 80 个名称,然后生成一个 cat 命令,将这些文件复制到单个目标文件,例如 "newfile.01";调整printf() 命令以适应您自己的命名/编号约定。然后将 shell 命令传递给 shell 执行。

    在测试时,将 sh -x 替换为空,或 sh -vn 或类似的东西。仅当您确定它会执行您想要的操作时才添加活动外壳。请记住,shell 脚本在运行时位于源目录中。

    从表面上看,xargs 命令会很好用;困难在于协调输出文件编号。可能有一种方法可以使用-n 80 选项来一次对 80 个文件进行分组,还可以通过一些奇特的方式来生成调用编号,但我不知道。

    另一种选择是使用xargs -n 执行一个shell 脚本,该脚本可以通过列出目标目录中已有的内容来推断正确的输出文件编号。这在许多方面会更干净:

    SOURCE="/path/to/dir"
    TARGET="/path/to/other/directory"
    (
    cd $SOURCE || exit 1
    ls |
    sort -t _ -k2,2n |
    xargs -n 80 cpfiles "$TARGET"
    )
    

    cpfiles 的样子:

    TARGET="$1"
    shift
    if [ $# -gt 0 ]
    then
        old=$(ls -r newfile.?? | sed -n -e 's/newfile\.//p; 1q')
        new=$(printf "%.2d" $((old + 1)))
        cat "$@" > "$TARGET/newfile. $new
    fi
    

    零参数测试避免了xargs 使用零参数执行命令一次的麻烦。总的来说,我更喜欢这个解决方案而不是使用 awk 的解决方案。

    【讨论】:

    • +1 这个答案可能更快,因为它涉及较少的 cat 调用。它还可以处理文件名编号中的空白
    • 太棒了,感谢您对它们如何组合在一起的深入解释。
    【解决方案4】:

    这是@chepner 的第一个解决方案的宏,使用GNU Make 作为模板语言:

    SHELL := /bin/bash
    N = 800
    B = 80
    
    fileNums = $(shell seq 1 $$((${N}/${B})) )
    files = ${fileNums:%=file%.txt}
    
    all: ${files}
    
    file%.txt : start = $(shell echo $$(( ($*-1)*${B}+1 )) )
    file%.txt : end = $(shell echo $$(( $* * ${B} )) )
    
    file%.txt:
            cat ath_{${start}..${end}} > $@
    

    使用方法:

    $ make -n all
    cat ath_{1..80} > file1.txt
    cat ath_{81..160} > file2.txt
    cat ath_{161..240} > file3.txt
    cat ath_{241..320} > file4.txt
    cat ath_{321..400} > file5.txt
    cat ath_{401..480} > file6.txt
    cat ath_{481..560} > file7.txt
    cat ath_{561..640} > file8.txt
    cat ath_{641..720} > file9.txt
    cat ath_{721..800} > file10.txt
    

    【讨论】:

      猜你喜欢
      • 2020-04-19
      • 1970-01-01
      • 2015-12-23
      • 1970-01-01
      • 2021-10-08
      • 2014-12-09
      • 1970-01-01
      • 1970-01-01
      • 2014-01-20
      相关资源
      最近更新 更多