4

我目前正在做一个数学项目,但在 bash 中编程时遇到了一点砖墙。

目前我有一个包含 800 个文本文件的目录,我想要做的是运行一个循环将前 80 个文件(_01 到 _80)放入一个新文件并保存在其他地方,然后是接下来的 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等?

干杯,

4

4 回答 4

5

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

#!/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

外部循环将遍历 1、81、161n等。内部循环将遍历i1 到 80,然后是 81 到 160 等。内部循环的主体只是将i第 th 文件的内容转储到标准输出,但是循环的聚合输出存储在文件 1 中,然后存储在文件 2 中,以此类推。

于 2013-03-03T16:58:08.123 回答
4

你可以尝试这样的事情:

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

还将循环更改为

for file in /dir/ath_*

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

于 2013-03-03T15:18:22.253 回答
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.

于 2013-03-03T15:31:24.387 回答
1

这是@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
于 2013-03-06T01:05:00.337 回答