xargs z przekierowaniem stdin / stdout

21

Chciałbym uruchomić:

./a.out < x.dat > x.ans

dla każdego * .dat pliku w katalogu A .

Jasne, można to zrobić za pomocą skryptu bash / python / cokolwiek, ale lubię pisać seksowne, jedno-liniowe. Wszystko, co mogłem osiągnąć to (wciąż bez żadnego standardowego):

ls A/*.dat | xargs -I file -a file ./a.out

Ale -a w xargs nie rozumie „pliku” replace-str.

Dziękuje Ci za pomoc.

Nikolay Vyahhi
źródło
Oprócz innych dobrych odpowiedzi tutaj, możesz użyć opcji -a GNU xargs.
James Youngman,

Odpowiedzi:

30

Przede wszystkim nie należy używać lsdanych wyjściowych jako listy plików . Użyj rozszerzenia powłoki lub find. Poniżej przedstawiono potencjalne konsekwencje niewłaściwego użycia ls + xargs i przykład prawidłowego xargsużycia.

1. Prosty sposób: dla pętli

Jeśli chcesz przetworzyć tylko pliki poniżej A/, wystarczy zwykła forpętla:

for file in A/*.dat; do ./a.out < "$file" > "${file%.dat}.ans"; done

2. pre1 Dlaczego nie   ls | xargs ?

Oto przykład tego, jak złe rzeczy mogą włączyć w przypadku korzystania lsz xargsdo pracy. Rozważ następujący scenariusz:

  • najpierw stwórzmy puste pliki:

    $ touch A/mypreciousfile.dat\ with\ junk\ at\ the\ end.dat
    $ touch A/mypreciousfile.dat
    $ touch A/mypreciousfile.dat.ans
    
  • zobacz pliki i nie zawierają one nic:

    $ ls -1 A/
    mypreciousfile.dat
    mypreciousfile.dat with junk at the end.dat
    mypreciousfile.dat.ans
    
    $ cat A/*
    
  • uruchom magiczne polecenie, używając xargs:

    $ ls A/*.dat | xargs -I file sh -c "echo TRICKED > file.ans"
    
  • wynik:

    $ cat A/mypreciousfile.dat
    TRICKED with junk at the end.dat.ans
    
    $ cat A/mypreciousfile.dat.ans
    TRICKED
    

Właśnie udało Ci się zastąpić oba mypreciousfile.dati mypreciousfile.dat.ans. Gdyby w tych plikach znajdowała się jakaś zawartość, zostałaby usunięta.


2. Używanie   xargs : właściwy sposób z  find 

Jeśli chcesz nalegać na użycie xargs, użyj -0(nazwy zakończone znakiem null):

find A/ -name "*.dat" -type f -print0 | xargs -0 -I file sh -c './a.out < "file" > "file.ans"'

Zwróć uwagę na dwie rzeczy:

  1. w ten sposób będziesz tworzyć pliki z .dat.anszakończeniem;
  2. to się zepsuje, jeśli jakaś nazwa pliku zawiera znak cudzysłowu ( ").

Oba problemy można rozwiązać przez inny sposób wywoływania powłoki:

find A/ -name "*.dat" -type f -print0 | xargs -0 -L 1 bash -c './a.out < "$0" > "${0%dat}ans"'

3. Wszystko zrobione wewnątrz find ... -exec

 find A/ -name "*.dat" -type f -exec sh -c './a.out < "{}" > "{}.ans"' \;

To ponownie tworzy .dat.anspliki i ulegnie uszkodzeniu, jeśli zawierają nazwy plików ". Aby to zrobić, użyj bashi zmień sposób wywoływania:

 find A/ -name "*.dat" -type f -exec bash -c './a.out < "$0" > "${0%dat}ans"' {} \;
rozcietrzewiacz
źródło
+1 za wzmiankę o nie analizowaniu wyniku ls.
rahmu
2
Opcja 2 psuje się, gdy nazwy plików zawierają „.
thiton
2
Bardzo dobry punkt, dzięki! Zaktualizuję odpowiednio.
rozcietrzewiacz
Chcę tylko wspomnieć, że jeśli zshjest używany jako powłoka (a SH_WORD_SPLIT nie jest ustawiony), wszystkie nieprzyjemne przypadki specjalne (białe spacje, „w nazwie pliku itp.)) Nie muszą być brane pod uwagę. Trywialność for file in A/*.dat; do ./a.out < $file > ${file%.dat}.ans ; donedziała we wszystkich przypadkach.
jofel
-1, ponieważ próbuję wymyślić, jak wykonać xargs ze standardem, a moje pytanie nie ma nic wspólnego z plikami lub find.
Mehrdad
2

Spróbuj zrobić coś takiego (składnia może się nieco różnić w zależności od używanej powłoki):

$ for i in $(find A/ -name \*.dat); do ./a.out < ${i} > ${i%.dat}.ans; done

rahmu
źródło
To by nie działało. Próbowałby działać na rzeczach takich jak somefile.dat.dati przekierowywać wszystkie dane wyjściowe do jednego pliku.
rozcietrzewiacz
Masz rację. Zredagowałem rozwiązanie, aby to poprawić.
rahmu
OK - Prawie dobrze :) somefile.dat.ansRzeczy wyjściowe nie wyglądałyby tak ładnie.
rozcietrzewiacz
1
Edytowane! Nie wiedziałem o „%”. Działa jak urok, dzięki za wskazówkę.
rahmu
1
Dodanie -type filebyłoby fajne (nie może < directory), a to sprawia, że ​​niezwykła nazwa pliku-bajki jest smutna.
Mat
2

W przypadku prostych wzorów odpowiednia jest pętla for:

for file in A/*.dat; do
    ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

W bardziej skomplikowanych przypadkach, gdy potrzebujesz innego narzędzia do wyświetlenia listy plików (zsh lub bash 4 mają wystarczająco mocne wzorce, których rzadko potrzebujesz znaleźć, ale jeśli chcesz pozostać w powłoce POSIX lub użyć szybkiej powłoki jak dash, będziesz musiał znaleźć cokolwiek nietrywialne), podczas gdy najbardziej odpowiednie jest czytanie:

find A -name '*.dat' -print | while IFS= read -r file; do
   ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Będzie to obsługiwać spacje, ponieważ odczyt jest (domyślnie) zorientowany liniowo. Nie będzie obsługiwał znaków nowej linii i nie będzie obsługiwał ukośników odwrotnych, ponieważ domyślnie interpretuje sekwencje specjalne (co w rzeczywistości umożliwia przejście do nowego wiersza, ale find nie może wygenerować tego formatu). Wiele powłok ma -0opcję odczytu, więc w tych można obsłużyć wszystkie znaki, ale niestety nie jest to POSIX.

Jan Hudec
źródło
2

Użyj GNU Parallel:

parallel ./a.out "<{} >{.}.ans" ::: A/*.dat

Dodano bonus: przetwarzanie odbywa się równolegle.

Obejrzyj filmy wprowadzające, aby dowiedzieć się więcej: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
źródło
1

Myślę, że potrzebujesz przynajmniej wywołania powłoki w xargs:

ls A/*.dat | xargs -I file sh -c "./a.out < file > file.ans"

Edycja: Należy zauważyć, że to podejście nie działa, gdy nazwy plików zawierają spacje. Nie działa Nawet jeśli użyjesz find -0 i xargs -0, aby xargs poprawnie zrozumiał spacje, wywołanie powłoki -c na nich zaskoczy. Jednak OP wyraźnie poprosił o rozwiązanie xargs i jest to najlepsze rozwiązanie xargs, jakie wymyśliłem. Jeśli spacje w nazwach plików mogą stanowić problem, użyj find -exec lub pętli powłoki.

Thiton
źródło
@rozcietrzewiacz: Ogólnie rzecz biorąc, jasne, ale zakładam, że ktoś próbujący zrobić vargoo xargs wie o tym. Ponieważ te nazwy plików podlegają kolejnej rozbudowie powłoki, i tak muszą być dobrze zachowane.
thiton,
1
Mylisz się co do rozszerzenia powłoki. lswyświetla rzeczy takie jak spacje bez ucieczki i to jest problem.
rozcietrzewiacz
@rozcietrzewiacz: Tak, rozumiem ten problem. Teraz załóżmy, że właściwie uciekniesz z tych spacji i umieścisz je w xargs: Zostaną one zastąpione ciągiem -c sh, sh tokenizuje ciąg -c i wszystko się psuje.
thiton,
Tak. Widzisz teraz, parsowanie lsnie jest dobre. Ale xargs można bezpiecznie używać z find - patrz sugestia nr 2 w mojej odpowiedzi.
rozcietrzewiacz
1

Nie trzeba tego komplikować. Możesz to zrobić za pomocą forpętli:

for file in A/*.dat; do
  ./a.out < "$file" >"${file%.dat}.ans"
done

${file%.dat}.ansnieco usunie .datprzyrostek nazwy pliku z pliku w $filei zamiast dodawać .ansdo końca.

Kusalananda
źródło