我有大约 15,000 个名为file_1.pdb
、file_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
,sort
和xargs
:
find . -maxdepth 1 -type f -name 'file_*.pdb' -print0 |
sort -zV |
xargs -0 cat >all.pdb
该find
命令找到所有相关文件,然后将它们的路径名打印出来,sort
进行“版本排序”以使它们按正确的顺序排列(如果文件名中的数字已被零填充到固定宽度,我们就不需要-V
)。xargs
获取此排序路径名列表并cat
尽可能大批量地运行这些路径名。
即使文件名包含奇怪的字符(例如换行符和空格),这也应该有效。我们使用-print0
withfind
给出以sort
nul 结尾的名称进行排序,并sort
使用 处理这些名称-z
。 xargs
也使用其标志读取以 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
,读取以xargs
null 结尾的记录。没有 POSIX 等价物。 POSIXly,需要以xargs
.
如果路径名表现良好,并且目录结构是扁平的(没有子目录),那么可以不用这些标志,除了-V
with 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 的全局运算符。如果没有x
nor y
,则它是任何十进制数。相当于extendedglob
s[0-9]##
或kshglob
s +([0-9])
(一位或多位数字))。
使用ksh93
, 使用其内置cat
命令(因此不受系统调用限制的影响,execve()
因为没有执行):
command /opt/ast/bin/cat file_{1..15000}.pdb > file_all.pdb
使用bash
// zsh
(ksh93
支持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] ...