Jak uruchomić polecenie dla każdej linii pliku?

161

Na przykład, teraz używam poniższego, aby zmienić kilka plików, których ścieżki uniksowe napisałem do pliku:

cat file.txt | while read in; do chmod 755 "$in"; done

Czy jest bardziej elegancki i bezpieczniejszy sposób?

jastrząb
źródło

Odpowiedzi:

127

Przeczytaj plik wiersz po wierszu i wykonaj polecenia: 4 odpowiedzi

Dzieje się tak, ponieważ nie ma tylko jednej odpowiedzi ...

  1. shell rozwinięcie wiersza poleceń
  2. xargs dedykowane narzędzie
  3. while read z kilkoma uwagami
  4. while read -uużycie dedykowanego fd, interaktywnego przetwarzania (przykład)

Odnośnie żądania OP: działającym chmodna wszystkich celach wymienionych w pliku , xargsjest wskazanym narzędziem. Ale w przypadku niektórych innych aplikacji, niewielkiej liczby plików itp.

  1. Czytaj cały plik jako argument wiersza poleceń.

    Jeśli twój plik nie jest zbyt duży i wszystkie pliki są dobrze nazwane (bez spacji lub innych znaków specjalnych, takich jak cudzysłowy), możesz użyć shellrozwinięcia wiersza poleceń . Po prostu:

    chmod 755 $(<file.txt)

    W przypadku małej liczby plików (linii) to polecenie jest lżejsze.

  2. xargs to właściwe narzędzie

    W przypadku większej liczby plików lub prawie dowolnej liczby wierszy w pliku wejściowym ...

    Dla wielu Binutilsw narzędzi, takich jak chown, chmod, rm, cp -t...

    xargs chmod 755 <file.txt

    Jeśli masz specjalne znaki i / lub dużo linii w file.txt.

    xargs -0 chmod 755 < <(tr \\n \\0 <file.txt)

    jeśli twoje polecenie musi zostać uruchomione dokładnie 1 raz przez wpis:

    xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)

    Nie jest to potrzebne w tym przykładzie, ponieważ chmodakceptujemy wiele plików jako argument, ale pasuje to do tytułu pytania.

    W niektórych przypadkach możesz nawet zdefiniować lokalizację argumentu plikowego w poleceniach generowanych przez xargs:

    xargs -0 -I '{}' -n 1 myWrapper -arg1 -file='{}' wrapCmd < <(tr \\n \\0 <file.txt)

    Testuj seq 1 5jako dane wejściowe

    Spróbuj tego:

    xargs -n 1 -I{} echo Blah {} blabla {}.. < <(seq 1 5)
    Blah 1 blabla 1..
    Blah 2 blabla 2..
    Blah 3 blabla 3..
    Blah 4 blabla 4..
    Blah 5 blabla 5..
    

    Gdzie polecenie jest wykonywane raz w wierszu .

  3. while read i warianty.

    Zgodnie z sugestią OP cat file.txt | while read in; do chmod 755 "$in"; donezadziała, ale są 2 problemy:

    • cat |jest bezużytecznym widelcem i

    • | while ... ;donestanie się podpowłoką, po której środowisko zniknie ;done.

    Więc można to lepiej napisać:

    while read in; do chmod 755 "$in"; done < file.txt

    Ale,

    • Możesz zostać ostrzeżony $IFSi readflagi:

      help read
      read: read [-r] ... [-d delim] ... [name ...]
          ...
          Reads a single line from the standard input... The line is split
          into fields as with word splitting, and the first word is assigned
          to the first NAME, the second word to the second NAME, and so on...
          Only the characters found in $IFS are recognized as word delimiters.
          ...
          Options:
            ...
            -d delim   continue until the first character of DELIM is read, 
                       rather than newline
            ...
            -r do not allow backslashes to escape any characters
          ...
          Exit Status:
          The return code is zero, unless end-of-file is encountered...
      

      W niektórych przypadkach może być konieczne użycie

      while IFS= read -r in;do chmod 755 "$in";done <file.txt

      Aby uniknąć problemów z obcymi nazwami plików. A może jeśli napotkasz problemy z UTF-8:

      while LANG=C IFS= read -r in ; do chmod 755 "$in";done <file.txt
    • Kiedy używasz STDINdo czytania file.txt, twój skrypt nie może być interaktywny (nie możesz STDINjuż go używać ).

  4. while read -u, używając dedykowanego fd.

    Składnia: while read ...;done <file.txtprzekieruje STDINdo file.txt. Oznacza to, że nie będziesz w stanie poradzić sobie z procesem, dopóki nie zakończą.

    Jeśli planujesz stworzyć narzędzie interaktywne , musisz unikać używania STDINalternatywnego deskryptora pliku i używać go .

    Deskryptory plików stałych to: 0dla STDIN , 1dla STDOUT i 2dla STDERR . Możesz je zobaczyć poprzez:

    ls -l /dev/fd/

    lub

    ls -l /proc/self/fd/

    Stamtąd musisz wybrać nieużywany numer, od 0do 63(więcej, w rzeczywistości, w zależności od sysctlnarzędzia superużytkownika) jako deskryptor pliku :

    W tym demo użyję fd 7 :

    exec 7<file.txt      # Without spaces between `7` and `<`!
    ls -l /dev/fd/
    

    Wtedy możesz użyć w read -u 7ten sposób:

    while read -u 7 filename;do
        ans=;while [ -z "$ans" ];do
            read -p "Process file '$filename' (y/n)? " -sn1 foo
            [ "$foo" ]&& [ -z "${foo/[yn]}" ]&& ans=$foo || echo '??'
        done
        if [ "$ans" = "y" ] ;then
            echo Yes
            echo "Processing '$filename'."
        else
            echo No
        fi
    done 7<file.txt
    

    done

    Aby zamknąć fd/7:

    exec 7<&-            # This will close file descriptor 7.
    ls -l /dev/fd/
    

    Uwaga: zezwalam na przekreśloną wersję, ponieważ ta składnia może być przydatna podczas wykonywania wielu operacji we / wy z procesem równoległym:

    mkfifo sshfifo
    exec 7> >(ssh -t user@host sh >sshfifo)
    exec 6<sshfifo
    
F. Hauri
źródło
3
Zgodnie z xargspoczątkową budową odpowiadającą na tego rodzaju potrzeby, niektóre funkcje, takie jak budowanie poleceń tak długo, jak to możliwe w obecnym środowisku, aby wywoływać chmodw tym przypadku jak najmniej, zmniejszając rozwidlenia zapewniają wydajność. while ;do..done <$fileimplie działa 1 fork na 1 plik. xargsmoże uruchomić 1 fork dla tysięcy plików ... w niezawodny sposób.
F. Hauri,
1
dlaczego trzecie polecenie nie działa w pliku makefile? Otrzymuję „błąd składni w pobliżu nieoczekiwanego tokenu„ <””, ale wykonywanie bezpośrednio z wiersza poleceń działa.
Woodrow Barlow
2
Wydaje się, że jest to związane ze składnią specyficzną dla Makefile. Możesz spróbować odwrócić linię poleceń:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
F. Hauri
@ F.Hauri z jakiegoś powodu tr \\n \\0 <file.txt |xargs -0 [command]jest około 50% szybsza niż metoda, którą opisałeś.
phil294
Październik 2019, nowa edycja, dodaj próbkę interaktywnego procesora plików.
F. Hauri
150

Tak.

while read in; do chmod 755 "$in"; done < file.txt

W ten sposób możesz uniknąć catprocesu.

catjest prawie zawsze złe w takim celu jak ten. Możesz przeczytać więcej o bezużytecznym użyciu Cat.

PP
źródło
Unikać jeden cat jest dobrym pomysłem, ale w tym przypadku, polecenie wskazane jestxargs
F. Hauri
Ten link nie wydaje się być istotny, być może zawartość strony internetowej uległa zmianie? Reszta odpowiedzi jest jednak niesamowita :)
starbeamrainbowlabs
@starbeamrainbowlabs Yes. Wygląda na to, że strona została przeniesiona. Podłączyłem ponownie i teraz powinno być dobrze. Dzięki :)
PP
1
Dzięki! Było to pomocne, zwłaszcza gdy trzeba było zrobić coś innego niż wywołanie chmod(tj. Naprawdę uruchomić jedno polecenie dla każdej linii w pliku).
Per Lundberg
uważaj na odwrotne ukośniki! from unix.stackexchange.com/a/7561/28160 - „ read -rczyta pojedynczą linię ze standardowego wejścia ( readbez -rinterpretacji odwrotnych ukośników, nie chcesz tego)”.
Ten Brazylijczyk
16

jeśli masz fajny selektor (na przykład wszystkie pliki .txt w katalogu), możesz zrobić:

for i in *.txt; do chmod 755 "$i"; done

bash for loop

lub Twój wariant:

while read line; do chmod 755 "$line"; done <file.txt
d.raev
źródło
Nie działa to, że jeśli w wierszu są spacje, dane wejściowe są dzielone spacjami, a nie wierszami.
Michael Fox
@Michael Fox: Linie ze spacjami mogą być obsługiwane przez zmianę separatora. Aby zmienić ją na znaki nowej linii, ustaw zmienną środowiskową „IFS” przed skryptem / poleceniem. Np .: export IFS = '$ \ n'
codeniffer
Literówka w moim ostatnim komentarzu. Powinno wyglądać następująco: export IFS = $ '\ n'
codeniffer
14

Jeśli wiesz, że nie masz żadnych białych znaków na wejściu:

xargs chmod 755 < file.txt

Jeśli w ścieżkach mogą znajdować się spacje i jeśli masz GNU xargs:

tr '\n' '\0' < file.txt | xargs -0 chmod 755
glenn jackman
źródło
Wiem o xargs, ale (niestety) wydaje się, że jest to mniej niezawodne rozwiązanie niż wbudowane funkcje bash, takie jak while i read. Nie mam też GNU xargs, ale używam OS X i xargs ma tutaj również opcję -0. Dziękuję za odpowiedź.
jastrząb
1
@hawk No: xargsjest solidny. To narzędzie jest bardzo stare, a jego kod jest mocno zmieniany . Jego celem było początkowo zbudowanie linii z uwzględnieniem ograniczeń powłoki (64kchar / wiersz lub coś podobnego). Teraz to narzędzie może pracować z bardzo dużymi plikami i może znacznie zmniejszyć liczbę rozwidleń do ostatecznego polecenia. Zobacz moją odpowiedź i / lub man xargs.
F. Hauri,
@hawk Mniej niezawodnego rozwiązania, w jaki sposób? Jeśli działa w systemie Linux, Mac / BSD i Windows (tak, pakiet MSYSGIT GNU xargs), to jest tak niezawodny, jak to tylko możliwe.
Camilo Martin
1
Dla tych, którzy nadal znajdują to w wynikach wyszukiwania ... możesz zainstalować GNU xargs na macOS za pomocą Homebrew ( brew install findutils), a następnie wywołać GNU xargs z gxargs, np.gxargs chmod 755 < file.txt
Jase
13

Jeśli chcesz uruchomić swoje polecenie równolegle dla każdej linii, możesz użyć GNU Parallel

parallel -a <your file> <program>

Każda linia twojego pliku zostanie przekazana do programu jako argument. Domyślnie paralleluruchamia tyle wątków, ile liczy Twoje procesory. Ale możesz to określić za pomocą-j

janisz
źródło
3

Widzę, że oznaczyłeś bash, ale Perl byłby również dobrym sposobem, aby to zrobić:

perl -p -e '`chmod 755 $_`' file.txt

Możesz również zastosować wyrażenie regularne, aby upewnić się, że otrzymujesz właściwe pliki, np. Aby przetwarzać tylko pliki .txt:

perl -p -e 'if(/\.txt$/) `chmod 755 $_`' file.txt

Aby „wyświetlić podgląd” tego, co się dzieje, wystarczy zamienić lewe apostrofy na podwójne cudzysłowy i dodać przed print:

perl -p -e 'if(/\.txt$/) print "chmod 755 $_"' file.txt
1.618
źródło
2
Dlaczego używać grawisów? Perl ma chmodfunkcję
glenn jackman
1
Chcesz perl -lpe 'chmod 0755, $_' file.txt- użyj -lfunkcji „auto-chomp”
glenn jackman
1

Możesz także użyć AWK, który daje większą elastyczność w obsłudze pliku

awk '{ print "chmod 755 "$0"" | "/bin/sh"}' file.txt

jeśli twój plik ma separator pól, taki jak:

pole1, pole2, pole3

Aby uzyskać tylko pierwsze pole, które robisz

awk -F, '{ print "chmod 755 "$1"" | "/bin/sh"}' file.txt

Więcej szczegółów można znaleźć w dokumentacji GNU https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple

brunocrt
źródło
0

Wiem, że jest późno, ale nadal

Jeśli przez przypadek napotkasz plik tekstowy zapisany w systemie Windows z \r\nzamiast \n, możesz się zdezorientować, jeśli twoja komenda ma sth po przeczytanej linii jako argument. Usuń więc \rna przykład:

cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
EP
źródło