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.


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"' {} \;

+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


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.



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.



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

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.