Wykonaj polecenie na wszystkich plikach w katalogu


290

Czy ktoś mógłby podać kod do wykonania następujących czynności: Załóżmy, że istnieje katalog plików, z których wszystkie muszą być uruchomione przez program. Program wypisuje wyniki na standardowe wyjście. Potrzebuję skryptu, który przejdzie do katalogu, wykona polecenie dla każdego pliku i połączy dane wyjściowe w jeden duży plik wyjściowy.

Na przykład, aby uruchomić polecenie dla 1 pliku:

$ cmd [option] [filename] > results.out

3
Chciałbym dodać do pytania. Czy można to zrobić za pomocą xargs? np. ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out
Ozair Kafray

2
Może, ale prawdopodobnie nie chcesz używaćls do prowadzenia pojazdu xargs. Jeśli cmdw ogóle jest napisany kompetentnie, być może możesz po prostu zrobić cmd <wildcard>.
tripleee

Odpowiedzi:


425

Poniższy kod bash przekaże $ file do polecenia, gdzie $ file będzie reprezentować każdy plik w / dir

for file in /dir/*
do
  cmd [option] "$file" >> results.out
done

Przykład

el@defiant ~/foo $ touch foo.txt bar.txt baz.txt
el@defiant ~/foo $ for i in *.txt; do echo "hello $i"; done
hello bar.txt
hello baz.txt
hello foo.txt

23
Jeśli nie ma żadnych plików /dir/, wówczas pętla nadal działa raz z wartością „*” dla $file, co może być niepożądane. Aby tego uniknąć, włącz nullglob na czas trwania pętli. Dodaj tę linię przed pętlą shopt -s nullglobi tę linię po pętli shopt -u nullglob #revert nullglob back to it's normal default state.
Gulasz-au

43
+1, I to tylko kosztowało mnie całą kolekcję tapet. wszyscy za mną używają podwójnych cudzysłowów. „$ file”
Behrooz

Jeśli plik wyjściowy jest taki sam w pętli, przekierowanie poza pętlę jest o wiele bardziej wydajne done >results.out(i prawdopodobnie wtedy możesz zastąpić zamiast dołączać, tak jak tutaj założyłem).
tripleee

Jak uzyskać poszczególne pliki wyników, które są niestandardowo nazwane w swoich plikach wejściowych?
Timothy Swan

1
bądź ostrożny, używając tego polecenia do ogromnej ilości plików w reż. Zamiast tego użyj find -exec.
kolisko

181

Co powiesz na to:

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
  • -maxdepth 1argument zapobiega rekurencyjnemu zejściu find do jakichkolwiek podkatalogów. (Jeśli chcesz przetworzyć takie zagnieżdżone katalogi, możesz to pominąć).
  • -type -f określa, że ​​będą przetwarzane tylko zwykłe pliki.
  • -exec cmd option {} każe mu działać cmd z podanym optiondla każdego znalezionego pliku, z zastąpioną nazwą pliku{}
  • \; oznacza koniec polecenia.
  • Wreszcie, wynik wszystkich osób cmd wykonań są przekierowywane do results.out

Jeśli jednak zależy Ci na kolejności przetwarzania plików, lepiej zapisz pętlę. Myślę, że findprzetwarza pliki w kolejności i-węzłów (chociaż mogę się mylić), co może nie być tym, czego chcesz.


1
To jest właściwy sposób przetwarzania plików. Korzystanie z pętli for jest podatne na błędy z wielu powodów. Również sortowania można dokonać za pomocą innych poleceń, takich jak stati sort, które oczywiście zależą od kryteriów sortowania.
tuxdna,

1
gdybym chciał uruchomić dwa polecenia, jak bym je połączyć po -execopcji? Czy muszę zawijać je w pojedyncze cudzysłowy czy coś?
frei

findjest zawsze najlepszą opcją, ponieważ możesz filtrować według wzorca nazwy pliku z opcją -namei możesz to zrobić za pomocą jednego polecenia.
João Pimentel Ferreira,

3
@frei odpowiedź na twoje pytanie jest tutaj: stackoverflow.com/a/6043896/1243247, ale w zasadzie po prostu dodaj -execopcje:find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;
João Pimentel Ferreira

2
jak możesz odwoływać się do nazwy pliku jako opcji?
Toskan,

54

Robię to na moim malinowym pi z wiersza poleceń, uruchamiając:

for i in *;do omxplayer "$i";done

7

Przyjęte / wysoko głosowane odpowiedzi są świetne, ale brakuje im kilku drobiazgowych szczegółów. Ten post omawia przypadki, w których lepiej radzić sobie, gdy rozszerzenie nazwy ścieżki powłoki (glob) kończy się niepowodzeniem, gdy nazwy plików zawierają osadzone symbole nowego wiersza / myślnika i przeniesienie wyjścia polecenia z pętli for podczas zapisywania wyników do plik.

Podczas uruchamiania rozszerzenia globu powłoki za pomocą *istnieje możliwość niepowodzenia rozszerzenia, jeśli w katalogu nie ma żadnych plików, a nierozwinięty ciąg globu zostanie przekazany do polecenia do uruchomienia, co może mieć niepożądane skutki. bashPowłoka zapewnia rozszerzoną opcję powłoki dla tego użyciem nullglob. Pętla zasadniczo wygląda następująco w katalogu zawierającym pliki

 shopt -s nullglob

 for file in ./*; do
     cmdToRun [option] -- "$file"
 done

Pozwala to bezpiecznie wyjść z pętli for, gdy wyrażenie ./*nie zwraca żadnych plików (jeśli katalog jest pusty)

lub w sposób zgodny z POSIX ( nullglobjest bashspecyficzny)

 for file in ./*; do
     [ -f "$file" ] || continue
     cmdToRun [option] -- "$file"
 done

Pozwala to wejść do pętli, gdy wyrażenie nie powiedzie się raz i warunek [ -f "$file" ]sprawdzi, czy nierozwinięty ciąg ./*jest prawidłową nazwą pliku w tym katalogu, co nie byłoby. Tak więc w tym przypadku błąd, przy użyciu continuewznawiamy z powrotem do forpętli, która nie będzie działać później.

Zwróć także uwagę na użycie --tuż przed przekazaniem argumentu nazwy pliku. Jest to konieczne, ponieważ, jak wspomniano wcześniej, nazwy plików powłoki mogą zawierać myślniki w dowolnym miejscu w nazwie pliku. Niektóre polecenia powłoki interpretują to i traktują je jako opcję polecenia, gdy nazwa nie jest poprawnie cytowana, i wykonują polecenie, zastanawiając się, czy flaga jest podana.

W takim przypadku --sygnalizuje koniec opcji wiersza poleceń, co oznacza, że ​​polecenie nie powinno analizować żadnych ciągów poza tym punktem jako flag poleceń, a jedynie jako nazwy plików.


Podwójne cytowanie nazw plików prawidłowo rozwiązuje przypadki, gdy nazwy zawierają znaki globalne lub białe znaki. Ale nazwy plików * nix mogą również zawierać w nich znaki nowej linii. Dlatego ograniczamy nazwy plików za pomocą jedynego znaku, który nie może być częścią prawidłowej nazwy pliku - null byte ( \0). Ponieważ bashwewnętrznie używa Cciągów stylów, w których do wskazania końca łańcucha używane są bajty zerowe, jest to odpowiedni kandydat na to.

Tak więc używając printfopcji powłoki do rozdzielenia plików tym bajtem NULL za pomocą -dopcji readpolecenia, możemy to zrobić poniżej

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done

nullglobI printfsą owinięte wokół (..)co oznacza, że są w zasadzie prowadzone w sub-shell (powłoka dziecko), ponieważ aby uniknąć nullglobmożliwości zastanowienia się na powłoce macierzystej, raz wyjść sterujących. -d ''Opcja readpolecenia jest nie POSIX zgodne, więc potrzebuje bashskorupę, aby to zrobić. Za pomocą findpolecenia można to zrobić jako

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)

W przypadku findimplementacji, które nie obsługują -print0(innych niż implementacje GNU i FreeBSD), można to emulować za pomocąprintf

find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --

Inną ważną poprawką jest przeniesienie zmiany kierunku poza pętlę for, aby zmniejszyć dużą liczbę operacji we / wy pliku. Gdy używana jest w pętli, powłoka musi wykonywać wywołania systemowe dwa razy dla każdej iteracji pętli for, raz dla otwarcia i raz dla zamknięcia deskryptora pliku skojarzonego z plikiem. Stanie się to wąskim gardłem w wydajności podczas wykonywania dużych iteracji. Zalecaną sugestią byłoby przeniesienie go poza pętlę.

Rozszerzając powyższy kod o te poprawki, możesz to zrobić

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done > results.out

który po prostu umieści zawartość polecenia dla każdej iteracji wejścia pliku na standardowe wyjście, a gdy pętla się zakończy, otwórz plik docelowy jeden raz, aby zapisać zawartość standardowego wejścia i zapisać go. Równoważna findwersja tego samego byłaby

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out

1
+1 za sprawdzenie, czy plik istnieje. Jeśli wyszukiwanie w nieistniejącym katalogu, $ plik zawiera ciąg wyrażenia regularnego „/ invald_dir / *”, nie jest prawidłową nazwą pliku.
cdalxndr

3

Jednym szybkim i brudnym sposobem, który czasami wykonuje zadanie, jest:

find directory/ | xargs  Command 

Na przykład, aby znaleźć liczbę wierszy we wszystkich plikach w bieżącym katalogu, możesz:

find . | xargs wc -l

8
@Hubert Dlaczego w nazwach plików masz znaki nowej linii ?!
musicin3d

2
to nie jest pytanie „dlaczego”, to kwestia poprawności - nazwy plików nie muszą zawierać znaków do wydrukowania, nie muszą nawet być prawidłowymi sekwencjami UTF-8. Ponadto to, co jest nową linią, jest bardzo zależne od kodowania, jedno kodowanie ♀ jest nową linią innego. Zobacz stronę kodową 437
Hubert Kario,

2
cmon, naprawdę? to działa 99,9% czasu, a on powiedział „szybki i brudny”
Edoardo

Nie jestem fanem „szybkich i brudnych” (AKA „zepsutych”) skryptów Bash. Wcześniej czy później kończy się na czymś w rodzaju słynnego „Moved ~/.local/share/steam. Ran steam. Skasował wszystko w systemie należącym do użytkownika”. Zgłoszenie błędu.
ograniczenie aktywności

To również nie będzie działać z plikami ze spacjami w nazwie.
Shamas S - Przywróć Monikę

2

Musiałem skopiować wszystkie pliki .md z jednego katalogu do drugiego, więc oto co zrobiłem.

for i in **/*.md;do mkdir -p ../docs/"$i" && rm -r ../docs/"$i" && cp "$i" "../docs/$i" && echo "$i -> ../docs/$i"; done

Co jest dość trudne do odczytania, więc podzielmy to.

najpierw cd do katalogu z plikami,

for i in **/*.md; dla każdego pliku we wzorze

mkdir -p ../docs/"$i"umieść ten katalog w folderze dokumentów poza folderem zawierającym pliki. Który tworzy dodatkowy folder o takiej samej nazwie jak ten plik.

rm -r ../docs/"$i" usuń dodatkowy folder utworzony w wyniku mkdir -p

cp "$i" "../docs/$i" Skopiuj aktualny plik

echo "$i -> ../docs/$i" Echo tego, co zrobiłeś

; done Żyj długo i szczęśliwie


Uwaga: **aby działać, globstarnależy ustawić opcję powłoki:shopt -s globstar
Hubert Kario

2

Możesz użyć xarg

ls | xargs -L 1 -d '\n' your-desired-command

-L 1 powoduje przejście 1 przedmiotu na raz

-d '\n'make wyjście lsjest podzielone na podstawie nowej linii.


1

W oparciu o podejście @Jima Lewisa:

Oto szybkie rozwiązanie wykorzystujące, finda także sortujące pliki według daty modyfikacji:

$ find  directory/ -maxdepth 1 -type f -print0 | \
  xargs -r0 stat -c "%y %n" | \
  sort | cut -d' ' -f4- | \
  xargs -d "\n" -I{} cmd -op1 {} 

Do sortowania patrz:

http://www.commandlinefu.com/commands/view/5720/find-files-and-list-them-sorted-by-modification-time


to nie zadziała, jeśli pliki mają nowe wiersze w swoich nazwach
Hubert Kario

1
@HubertKario Możesz przeczytać więcej informacji -print0na temat dla findi -0dla xargsktórych należy używać znaku null zamiast jakichkolwiek białych znaków (w tym znaków nowej linii).
tuxdna

tak, używanie -print0jest czymś, co pomaga, ale cały rurociąg musi używać czegoś takiego, a sortnie jest
Hubert Kario

1

myślę, że proste rozwiązanie to:

sh /dir/* > ./result.txt

2
Czy dobrze zrozumiałeś pytanie? Spowoduje to jedynie uruchomienie każdego pliku w katalogu przez powłokę - jakby to był skrypt.
rdas 16.04.19

1

Maksymalna głębokość

Przekonałem się, że działa to dobrze z odpowiedzią Jima Lewisa, wystarczy dodać coś takiego:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
$ find . -maxdepth 1 -type f -name '*.sh' -exec {} \; > results.out

Porządek sortowania

Jeśli chcesz wykonać w kolejności sortowania, zmodyfikuj go w następujący sposób:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -maxdepth 2 -type f -name '*.sh' | sort | bash > results.out

Na przykład będzie to wykonywane w następującej kolejności:

bash: 1: ./assets/main.sh
bash: 2: ./builder/clean.sh
bash: 3: ./builder/concept/compose.sh
bash: 4: ./builder/concept/market.sh
bash: 5: ./builder/concept/services.sh
bash: 6: ./builder/curl.sh
bash: 7: ./builder/identity.sh
bash: 8: ./concept/compose.sh
bash: 9: ./concept/market.sh
bash: 10: ./concept/services.sh
bash: 11: ./product/compose.sh
bash: 12: ./product/market.sh
bash: 13: ./product/services.sh
bash: 14: ./xferlog.sh

Nieograniczona głębokość

Jeśli chcesz wykonać na nieograniczonej głębokości pod pewnymi warunkami, możesz użyć tego:

export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -type f -name '*.sh' | sort | bash > results.out

następnie umieść na wierzchu każdego pliku w katalogach potomnych w następujący sposób:

#!/bin/bash
[[ "$(dirname `pwd`)" == $DIR ]] && echo "Executing `realpath $0`.." || return

i gdzieś w treści pliku nadrzędnego:

if <a condition is matched>
then
    #execute child files
    export DIR=`pwd`
fi
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.