web-dev-qa-db-ja.com

非常に多くのファイルを正しい順序でまとめます

file_1.pdbfile_2.pdbなどの名前のファイルが約15,000個あります。次のようにすることで、これらのうち数千個を順番に並べることができます。

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

ただし、これを15,000ファイルに対して実行すると、エラーが発生します

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

find . -name xx -exec xxを実行することでこの問題が解決されるのを見てきましたが、これではファイルが結合される順序が保持されません。どうすればこれを達成できますか?

23
sodiumnitrate

findsortxargsの使用:

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

findコマンドは、関連するすべてのファイルを検索してから、それらのパス名をsortに出力し、「バージョンの並べ替え」を行って正しい順序でファイルを取得します(ファイル名の数値が固定幅にゼロで埋められていた場合は、不要でした。 _-V_)。 xargsは、このソートされたパス名のリストを受け取り、これらに対してcatをできるだけ大きなバッチで実行します。

これは、ファイル名に改行やスペースなどの奇妙な文字が含まれている場合でも機能します。 findで_-print0_を使用してsortのヌル終了名を並べ替え、sortが_-z_を使用してこれらを処理します。 xargsも、_-0_フラグを使用してヌル文字で終了する名前を読み取ります。

名前がパターン_file_*.pdb_と一致しないファイルに結果を書き込んでいることに注意してください。


上記のソリューションは、いくつかのユーティリティにいくつかの非標準フラグを使用しています。これらは、これらのユーティリティのGNU実装によって、および少なくともOpenBSDとmacOS実装によってサポートされています。

使用される非標準フラグは次のとおりです。

  • _-maxdepth 1_、findを作成するには、最上位のディレクトリのみを入力し、サブディレクトリは入力しません。 POSIXly、_find . ! -name . -Prune ..._を使用
  • _-print0_、findにヌル終了パス名を出力させる(これはPOSIXでは考慮されていましたが拒否されました)。代わりに_-exec printf '%s\0' {} +_を使用できます。
  • _-z_、sortにNUL終了レコードを取得させる。 POSIXと同等のものはありません。
  • _-V_、sortを並べ替えます。 _200_の後の_3_。同等のPOSIXはありませんが、ファイル名に固定のプレフィックスが付いている場合は、ファイル名の特定の部分を数値で並べ替えることができます。
  • _-0_、xargsにヌル終了レコードを読み取らせる。 POSIXと同等のものはありません。 POSIXlyでは、xargsで認識される形式でファイル名を引用符で囲む必要があります。

パス名が適切に動作し、ディレクトリ構造がフラット(サブディレクトリなし)の場合、_-V_とsortを除いて、これらのフラグなしで実行できます。

49
Kusalananda

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までの10進数に一致するグロブ演算子です。xyもない場合は、10進数です。extendedglob[0-9]##またはkshglob+([0-9])(1桁以上))。

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
14

Forループは可能で、非常に単純です。

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

マイナス面は、catを何度も呼び出すことです。しかし、findを使用して正確に行う方法を思い出せず、呼び出しのオーバーヘッドが状況でそれほど悪くない場合は、覚えておく価値があります。

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

前提

特定の名前形式のonly15kファイルについては、このエラーが発生することはありません [ 12 ]

別のディレクトリからその拡張を実行していて、各ファイルへのパスを追加する必要がある場合、コマンドのサイズが大きくなり、もちろんそれが発生する可能性があります。

Solutionそのディレクトリからコマンドを実行します。

(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を除きます)。

いくつかの言葉

シェルコマンドラインに渡せる時間が長くなるはずです。
コマンドラインは213914文字で、15003の単語が含まれています
cat file_{1..15000}.pdb " > file_all.pdb" | wc

...各Wordに8バイトを追加しても、カーネル3.13.0でARG_MAXによって報告された2097142(2.1M)からはるかに下の333 938バイト(0.3M)またはわずかに小さい2088232 "実際に使用できるコマンドの最大長"by 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を完全に取り除くことができます [] (coreutilsから)、bashの変数を直接操作するか、Pythonを使用するか、cプログラムをコンパイルしてそれを実行します [ 4 ]...

2
Hastur

それを行う別の方法は

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
0
glglgl