以正确的顺序整理大量文件


23

我有大约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但这不会保留文件连接的顺序。我该如何实现?


3
第十个文件叫什么名字?(或任何具有多于一个数字编号顺序的文件。)
roaima

我(现在)在目录中有15,000个这些文件,您的cat file_{1..15000}.pdb构造对我来说很好。
roaima '18

11
取决于系统的限制。getconf ARG_MAX应该告诉。
ilkkachu

3
考虑将您的问题更改为“成千上万”或“大量”文件。可能会使有类似问题的其他人更容易找到问题。
msouth

Answers:


49

使用findsortxargs

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

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

即使文件名包含奇怪的字符(例如换行符和空格),这也应该起作用。我们使用-print0with find给出sort以n结尾的名称进行排序,并sort使用进行处理-zxargs还会读取带有-0标志的以nul结尾的名称。

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


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

使用的非标准标志是

  • -maxdepth 1find仅输入最顶层的目录,而不输入子目录。POSIXly,使用find . ! -name . -prune ...
  • -print0,以find输出以nul结尾的路径名(POSIX认为此路径名但被拒绝)。一个可以-exec printf '%s\0' {} +代替。
  • -z,以sort记录零终止的记录。没有POSIX等效项。
  • -V,使sort排序如2003。没有POSIX等效项,但是如果文件名具有固定的前缀,则可以用数字排序代替文件名的特定部分。
  • -0,以xargs读取未终止的记录。没有POSIX等效项。POSIXly,需要用识别的格式引用文件名xargs

如果路径名很乖,如果目录结构是平的(无子目录),那么可以凑合没有这些标志,除了-Vsort


1
您不需要为此使用非标准的null终止。这些文件名非常无聊,并且POSIX工具完全能够处理这些文件。
凯文

6
您也可以使用asker的规范(printf ‘file_%d.pdb\0’ {1..15000} | xargs -0 cat甚至是Kevin的观点)来更简洁地编写此代码echo file_{1..15000}.pdb | xargs catfind由于必须在文件系统中搜索那些文件,因此该解决方案的开销要大得多,但是当某些文件可能不存在时,该解决方案会更有用。
kojiro

4
@Kevin虽然您的说法是正确的,但最好是有一个适用于更一般情况的答案。在接下来的数千个人中,有一个问题可能是其中一些人的文件名中会有空格或其他内容。
msouth

1
@chrylis重定向从来都不是命令参数的一部分,它xargs不是cat重定向的对象(每次cat调用将使用xargs标准输出)。如果我们说过的xargs -0 sh -c 'cat >all.pdb'话,那么使用>>代替会很有意义>,这正是您所暗示的。
库沙兰丹

1
看起来sort -n -k1.6可以工作(适用于原始file_nnn文件名或sort -n -k1.5不带下划线的文件名)。
斯科特,

14

使用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>有一个glob运算符,它在十进制数字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/ zsh/ ksh93(其支撑zsh{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

seq -f | xarg cat > 是最优雅,最有效的解决方案。(恕我直言)。
哈斯图

检查棘手的文件名...也许 '"./-It'\''s a trickier filename - %.17g.pdb"'吗?
哈斯图

@Hastur,哎呀!是的,谢谢,我将其更改为替代的引用语法。您的也将工作。
斯特凡Chazelas

11

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

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

缺点是您会调用cat很多次地狱。但是,如果您不记得确切的操作方法,find并且调用开销在您的情况下还算不错,那么请牢记这一点。


我经常echo $i;在循环正文中添加“进度指示器”
Rolf

3
seq 1 15000 | awk '{print "file_"$0".dat"}' | xargs cat > file_all.pdb

1
awk可以在这里完成seq的工作,而seq可以完成awk的工作:seq -f file_%.10g.pdb 15000。请注意,这seq不是标准命令。
斯特凡·查泽拉斯18'Feb 27'9

谢谢史蒂芬(Stéphane)-我认为这 seq -f 是一种很好的方法。会记住的。
拉里C

2

前提

你不应该在错误招致 15K与特定的名称格式文件[ 12 ]

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

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

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

最佳解决方案如果反而我猜不好,则从文件所在的目录中运行它……
恕我直言,最佳解决方案是StéphaneChazelas的解决方案:

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个字节也要比3333.1 ARG_MAX内核上报告的2097142(2.1M)少333 938字节(0.3M),或者比报告为“我们实际上可以使用”通过xargs --show-limits

看看您的系统上的输出

getconf ARG_MAX
xargs --show-limits

懒惰引导解决方案

在这种情况下,即使通常会提供一种省时的解决方案,我还是更喜欢使用块。
逻辑(如果有的话)是我懒得写1 ... 1000 1001..2000等...
所以我要脚本来帮我。
仅在检查输出正确之后,才将其重定向到脚本。

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

请注意,由于文件名是受控的(没有空格,换行符...),因此您可以轻松使用下面的脚本。

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 ] ...


请注意,这%g是的缩写%.6g。例如,它将表示1,000,000为1e + 06。
斯特凡Chazelas

真懒人们使用专为任务工作围绕的工具,E2BIG限制一样xargs,zsh中的zargsksh93command -x
斯特凡Chazelas

seq不是内置的bash,而是来自GNU coreutils的命令。seq -f %g 1000000 1000000即使在最新版的coreutils中也输出1e + 06。
斯特凡Chazelas

@StéphaneChazelas 懒惰是一种精神状态。奇怪的是,但是当我看到(并目视检查序列化命令的输出)然后重定向到执行时,我感到更加自在。这种结构让我思考的事情比xarg... 少,但我知道它是个人的,也许只与我有关。
哈斯图

@StéphaneChazelasGotcha,对...固定。谢谢。我只用OP给出的15k文件进行了测试,这很糟糕。
哈斯图

0

另一种方法可能是

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.