前提

前提

我有大约 15,000 个名为file_1.pdbfile_2.pdb等的文件。我可以通过执行以下操作按顺序整理其中的大约几千个文件:

cat file_{1..2000}.pdb >> file_all.pdb

但是,如果我对 15,000 个文件执行此操作,则会收到错误

-bash: /bin/cat: Argument list too long

我已经看到这个问题通过这样做解决了find . -name xx -exec xx,但这不会保留文件连接的顺序。我怎样才能实现这个目标?

答案1

使用find,sortxargs:

find . -maxdepth 1 -type f -name 'file_*.pdb' -print0 |
sort -zV |
xargs -0 cat >all.pdb

find命令找到所有相关文件,然后将它们的路径名打印出来,sort进行“版本排序”以使它们按正确的顺序排列(如果文件名中的数字已被零填充到固定宽度,我们就不需要-V)。xargs获取此排序路径名列表并cat尽可能大批量地运行这些路径名。

即使文件名包含奇怪的字符(例如换行符和空格),这也应该有效。我们使用-print0withfind给出以sortnul 结尾的名称进行排序,并sort使用 处理这些名称-zxargs也使用其标志读取以 null 结尾的名称-0

请注意,我将结果写入名称与模式不匹配的文件file_*.pdb


上述解决方案对某些实用程序使用了一些非标准标志。这些实用程序的 GNU 实现以及至少 OpenBSD 和 macOS 实现都支持这些功能。

使用的非标准标志是

  • -maxdepth 1,只find进入最顶层目录,不进入子目录。 POSIXly,使用find . ! -name . -prune ...
  • -print0,使find输出以 nul 结尾的路径名(POSIX 考虑过但拒绝了)。可以用它-exec printf '%s\0' {} +来代替。
  • -z,以获取sort以 null 结尾的记录。没有 POSIX 等价物。
  • -V,进行sort排序,例如200在 之后3。不存在 POSIX 等效项,但如果文件名具有固定前缀,则可以用文件名特定部分的数字排序来替换。
  • -0,读取以xargsnull 结尾的记录。没有 POSIX 等价物。 POSIXly,需要以xargs.

如果路径名表现良好,并且目录结构是扁平的(没有子目录),那么可以不用这些标志,除了-Vwith sort

答案2

With zsh(该{1..15000}运算符来自哪里):

autoload zargs # best in ~/.zshrc
zargs file_{1..15000}.pdb -- cat > file_all.pdb

或者对于file_<digits>.pdb按数字顺序排列的所有文件:

zargs file_<->.pdb(n) -- cat > file_all.pdb

(其中<x-y>是匹配十进制数 x 到 y 的全局运算符。如果没有xnor y,则它是任何十进制数。相当于extendedglobs[0-9]##kshglobs +([0-9])(一位或多位数字))。

使用ksh93, 使用其内置cat命令(因此不受系统调用限制的影响,execve()因为没有执行):

command /opt/ast/bin/cat file_{1..15000}.pdb > file_all.pdb

使用bash// zshksh93支持zsh's{x..y}并且有printf内置):

printf '%s\n' file_{1..15000}.pdb | xargs cat > file_all.pdb

在 GNU 系统或兼容系统上,您还可以使用seq

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

对于xargs基于 - 的解决方案,必须特别注意包含空格、单引号、双引号或反斜杠的文件名。

类似于-It's a trickier filename - 12.pdb,使用:

seq -f "\"./-It's a trickier filename - %.17g.pdb\"" 15000 |
  xargs cat > file_all.pdb

答案3

for 循环是可能的,而且非常简单。

for i in file_{1..15000}.pdb; do cat $i >> file_all.pdb; done

缺点是你会调用cat很多次。但是,如果您无法准确记住如何执行这些操作find,并且在您的情况下调用开销并不算太糟糕,那么值得记住。

答案4

前提

你不应该犯这个错误仅有的具有该特定名称格式的 15k 个文件[1,2]

如果您从另一个目录运行该扩展,并且必须添加每个文件的路径,则命令的大小将会更大,当然这种情况可能会发生。

解决方案从该目录运行命令。

(cd That/Directory ; cat file_{1..2000}.pdb >> file_all.pdb )

最佳解决方案相反,如果我猜错了并且您从文件所在的目录运行它......
恕我直言,最好的解决方案是史蒂芬·查泽拉斯 (Stéphane Chazelas) 的作品:

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

与 printf 或 seq 一起使用;在 15k 文件上进行测试,仅预缓存其数量,它甚至是更快的文件(目前,除了文件所在目录中的 OP 文件之外)。

多说几句

您应该能够传递更长的 shell 命令行。
您的命令行长度为 213914 个字符,包含 15003
cat file_{1..15000}.pdb " > file_all.pdb" | wc

...即使为每个字添加 8 个字节也是 333 938 字节 (0.3M),远低于内核 3.13.0 报告的 2097142 (2.1M)ARG_MAX或报告为稍小的 2088232“我们实际可以使用的命令的最大长度”经过xargs --show-limits

在您的系统上查看以下输出

getconf ARG_MAX
xargs --show-limits

懒惰引导解决方案

在这种情况下,我更喜欢使用块,因为通常会得出一个省时的解决方案。
逻辑(如果有的话)是我懒得写 1...1000 1001..2000 等等...
所以我要求一个脚本为我做这件事。
只有在检查输出的正确性后,我才会将其重定向到脚本。

...但懒惰是一种心态
由于我对xargs(我真的应该xargs在这里使用)过敏并且我不想检查如何使用它,所以我准时完成了重新发明轮子的工作,如下例所示(tl;dr)。

请注意,由于文件名是受控制的(没有空格、换行符...),您可以轻松使用如下脚本之类的内容。

太长了;博士

版本 1:作为可选参数传递第一个文件号、最后一个文件号、块大小、输出文件

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;  
    cat $(seq -f file_%.17g.pdb $CurrentStart $CurrentEnd)  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    cat $(seq -f file_%.17g.pdb $CurrentStart $EndN)  >> $OutFile;

版本2

调用 bash 进行扩展(在我的测试中有点慢 ~20%)。

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;
    echo  cat file_{$CurrentStart..$CurrentEnd}.pdb | /bin/bash  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    echo  cat file_{$CurrentStart..$EndN}.pdb | /bin/bash  >> $OutFile;

当然你可以继续前进,彻底摆脱seq [3](来自 coreutils)并直接使用 bash 中的变量,或使用 python,或编译 ac 程序来执行此操作[4] ...

相关内容