przechwytuj bardzo dużą liczbę plików we właściwej kolejności


23

Mam około 15000 plików o nazwach file_1.pdb, file_2.pdbitp mogę kot o kilka tysięcy z nich w kolejności wykonując:

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

Jeśli jednak zrobię to dla 15 000 plików, pojawi się błąd

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

Widziałem, jak ten problem został rozwiązany, find . -name xx -exec xxale nie zachowałoby to kolejności łączenia plików. Jak mogę to osiągnąć?


3
Jak nazywa się dziesiąty plik? (Lub dowolny plik z więcej niż jednocyfrowym numerem porządkowym.)
roaima

Mam (teraz) 15 000 tych plików w katalogu, a twoja cat file_{1..15000}.pdbkonstrukcja działa dla mnie dobrze.
roaima

11
zależy od systemu, jaki jest limit. getconf ARG_MAXpowinien powiedzieć.
ilkkachu

3
Zastanów się nad zmianą pytania na „tysiące” lub „bardzo dużą liczbę” plików. Może to ułatwić znalezienie pytania innym osobom z podobnym problemem.
msouth

Odpowiedzi:


49

Korzystanie find, sorti xargs:

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

findPolecenie znajdzie wszystkie istotne pliki, a następnie wydrukowanie ich ścieżek się sort, że robi „wersji rodzaju”, aby uzyskać je w odpowiedniej kolejności (jeśli numery w nazwach było zero-wypełnione stałą szerokość nie byłby potrzebny -V). xargspobiera tę listę posortowanych nazw ścieżek i uruchamia catje w tak dużych partiach, jak to możliwe.

Powinno to działać, nawet jeśli nazwy plików zawierają dziwne znaki, takie jak znaki nowej linii i spacje. Używamy -print0z finddo nadawania sortnazw zakończonych zerami do sortowania, i sortposługujemy się nimi za pomocą -z. xargsteż odczytuje nazwy zakończone znakiem NUL ze swoją -0flagą.

Zauważ, że piszę wynik do pliku, którego nazwa nie pasuje do wzorca file_*.pdb.


Powyższe rozwiązanie wykorzystuje niektóre niestandardowe flagi dla niektórych narzędzi. Są one obsługiwane przez implementację GNU tych narzędzi oraz przynajmniej przez OpenBSD i implementację macOS.

Stosowane są niestandardowe flagi

  • -maxdepth 1, aby wprowadzić findtylko najwyższy katalog, ale nie podkatalogi. POSIXly, użyjfind . ! -name . -prune ...
  • -print0, aby utworzyć findwyjściowe nazwy zakończone wartością zerową (było to rozważane przez POSIX, ale odrzucone). Można -exec printf '%s\0' {} +zamiast tego użyć .
  • -z, aby robić sortrekordy zakończone zerami. Brak równoważności POSIX.
  • -V, aby sortposortować np . 200po 3. Nie ma odpowiednika POSIX, ale można go zastąpić sortowaniem numerycznym w określonych częściach nazwy pliku, jeśli nazwy plików mają stały prefiks.
  • -0, aby dokonać xargsodczytu zapisów zakończonych zerami. Brak równoważności POSIX. POSIXly, należałoby zacytować nazwy plików w formacie rozpoznawanym przez xargs.

Jeśli ścieżki: są dobrze wychowane, a jeśli struktura katalogów jest płaska (bez podkatalogów), wówczas można by obejść się bez tych flag, z wyjątkiem -Vz sort.


1
Nie potrzebujesz do tego niestandardowego zakończenia zerowego. Te nazwy plików są wyjątkowo nudne, a narzędzia POSIX są wtedy w stanie w pełni obsłużyć.
Kevin

6
Można również napisać to bardziej zwięźle ze specyfikacją Pytający jako printf ‘file_%d.pdb\0’ {1..15000} | xargs -0 cat, lub nawet z punktu Kevina, echo file_{1..15000}.pdb | xargs cat. findRozwiązanie ma znacznie więcej napowietrznych ponieważ ma przeszukać system plików dla tych plików, ale jest bardziej przydatna, gdy niektóre pliki mogą nie istnieć.
kojiro

4
@Kevin, chociaż to, co mówisz, jest prawdą, prawdopodobnie lepiej jest mieć odpowiedź, która ma zastosowanie w bardziej ogólnych okolicznościach. Z następnych tysięcy osób, które mają to pytanie, prawdopodobne jest, że niektóre z nich będą miały spacje lub cokolwiek w nazwach plików.
msouth

1
@chrylis Przekierowanie nigdy nie jest częścią argumentów polecenia i jest xargsraczej catprzekierowywane (każde catwywołanie będzie korzystało ze xargsstandardowego wyjścia). Gdybyśmy powiedzieli, xargs -0 sh -c 'cat >all.pdb'wtedy sensowniej byłoby użyć >>zamiast tego >, jeśli o to ci chodzi.
Kusalananda

1
Wygląda na to, sort -n -k1.6że działałoby (dla oryginału, file_nnnnazw plików lub sort -n -k1.5dla tych bez podkreślenia).
Scott

14

Z zsh(skąd {1..15000}pochodzi ten operator):

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

Lub dla wszystkich file_<digits>.pdbplików w kolejności numerycznej:

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

(gdzie <x-y>jest operatorem globalnym, który dopasowuje liczby dziesiętne x do y. Bez, xani yżadna liczba dziesiętna. Równoważna do extendedglob's [0-9]##lub kshglob' +([0-9])(jedna lub więcej cyfr)).

Za ksh93pomocą wbudowanego catpolecenia (więc nie ma wpływu na limit execve()wywołania systemowego, ponieważ nie ma wykonania ):

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

Z bash/ zsh/ ksh93(których wsparcie zshjest {x..y}i mają printfwbudowane):

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

W systemie GNU lub zgodnym możesz również użyć seq:

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

W przypadku xargsrozwiązań opartych na rozwiązaniach specjalnych należy zachować szczególną ostrożność w przypadku nazw plików zawierających spacje, pojedyncze lub podwójne cudzysłowy lub odwrotne ukośniki.

Jak dla -It's a trickier filename - 12.pdb, użyj:

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

To seq -f | xarg cat > najbardziej eleganckie i skuteczne rozwiązanie. (MOIM ZDANIEM).
Hastur

Sprawdź trudniejszą nazwę pliku ... może '"./-It'\''s a trickier filename - %.17g.pdb"'?
Hastur

@Hastur, ups! Tak, dziękuję, zmieniłem go na alternatywną składnię cytowania. Twoje też by działało.
Stéphane Chazelas

11

Pętla for jest możliwa i bardzo prosta.

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

Minusem jest to, że wywołujesz catpiekło wiele razy. Ale jeśli nie pamiętasz dokładnie, jak to zrobić, finda koszt wywołania nie jest taki zły w twojej sytuacji, warto o tym pamiętać.


Często dodam „a” echo $i;w treści pętli jako „wskaźnik postępu”
Rolf

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

1
awk może zrobić pracę SEK tu i nast może zrobić pracę awk jest: seq -f file_%.10g.pdb 15000. Zauważ, że seqto nie jest standardowe polecenie.
Stéphane Chazelas

Dzięki Stéphane - Myślę, że seq -f to świetny sposób, aby to zrobić; zapamięta to.
LarryC

2

Przesłanka

Nie powinieneś ponosić tego błędu tylko dla 15k plików o tym formacie nazw [ 1 , 2 ] .

Jeśli używasz tego rozszerzenia z innego katalogu i musisz dodać ścieżkę do każdego pliku, rozmiar twojego polecenia będzie większy i oczywiście może się zdarzyć.

Rozwiązanie uruchom komendę z tego katalogu.

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

Najlepsze rozwiązanie Jeśli zamiast tego zgadłem źle i uruchomisz go z katalogu, w którym znajdują się pliki ...
IMHO najlepszym rozwiązaniem są te Stéphane Chazelas :

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

z printf lub seq; przetestowany na 15k plików z tylko ich liczbą w pamięci podręcznej, jest nawet szybszy (obecnie i oprócz OP z tego samego katalogu, w którym znajdują się pliki).

Jeszcze kilka słów

Powinieneś być w stanie przejść do linii poleceń powłoki dłużej.
Twój wiersz poleceń ma 213914 znaków i zawiera 15003 słów
cat file_{1..15000}.pdb " > file_all.pdb" | wc

... nawet dodanie 8 bajtów dla każdego słowa to 333 938 bajtów (0,3 M) znacznie poniżej 2097142 (2,1 M) zgłoszonych przez ARG_MAXjądro 3.13.0 lub nieco mniejszy 2088232 zgłoszony jako „Maksymalna długość polecenia, którą moglibyśmy faktycznie użyj „ przezxargs --show-limits

Spójrz na swój system na wyjście

getconf ARG_MAX
xargs --show-limits

Lenistyczne rozwiązanie kierowane

W takich przypadkach wolę pracować z blokami, nawet jeśli zwykle wychodzi to na czas.
Logika (jeśli w ogóle) jest taka, że ​​jestem zbyt leniwy, aby napisać 1 ... 1000 1001..2000 itd. Itd.
Więc proszę skrypt, aby to dla mnie zrobił.
Dopiero po sprawdzeniu poprawności danych wyjściowych przekierowuję je do skryptu.

... ale lenistwo jest stanem umysłu .
Ponieważ jestem uczulony na xargs(naprawdę powinienem był go xargstutaj użyć ) i nie chcę sprawdzać, jak z niego korzystać, punktualnie kończę, aby wymyślić koło ponownie, jak w poniższych przykładach (tl; dr).

Zwróć uwagę, że ponieważ nazwy plików są kontrolowane (bez spacji, znaków nowej linii ...), możesz łatwo przejść z czymś w rodzaju skryptu poniżej.

tl; dr

Wersja 1: przekazuje jako parametr opcjonalny 1. numer pliku, ostatni, rozmiar bloku, plik wyjściowy

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

Wersja 2

Wywoływanie bash dla rozszerzenia (nieco wolniej w moich testach ~ 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;

Oczywiście możesz iść do przodu i całkowicie pozbyć się seq [ 3 ] (z coreutils) i pracować bezpośrednio ze zmiennymi w bash, lub użyć Pythona lub skompilować program ac, aby to zrobić [ 4 ] ...


Zauważ, że %gto skrót %.6g. Oznaczałoby to na przykład 1 000 000 jako 1e + 06.
Stéphane Chazelas

Naprawdę leniwi ludzie używają narzędzi zaprojektowanych do obejścia tego ograniczenia E2BIG, takich jak xargs, zsh zargslub ksh93's command -x.
Stéphane Chazelas

seqto nie jest wbudowane bash, to polecenie z jądra GNU. seq -f %g 1000000 1000000wyprowadza 1e + 06 nawet w najnowszej wersji coreutils.
Stéphane Chazelas

@ StéphaneChazelas Lenistwo to stan umysłu. Dziwne, ale czuję się bardziej przytulnie, kiedy widzę (i wizualnie sprawdzam wyjście z serializowanego polecenia) i dopiero wtedy przekierowuję do wykonania. Ta konstrukcja pozwala mi myśleć mniej niż xarg... ale rozumiem, że jest to sprawa osobista i być może związana tylko ze mną.
Hastur

@ StéphaneChazelas Gotcha, prawo ... Naprawiono. Dzięki. Testowałem tylko z plikami 15k podanymi przez OP, moje złe.
Hastur

0

Innym sposobem na to może być

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.