Come si esegue un comando per ogni riga di un file?


161

Ad esempio, in questo momento sto usando quanto segue per cambiare un paio di file i cui percorsi Unix ho scritto in un file:

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

C'è un modo più elegante e più sicuro?

Risposte:


127

Leggi un file riga per riga ed esegui i comandi: 4 risposte

Questo perché non esiste una sola risposta ...

  1. shell espansione della riga di comando
  2. xargs strumento dedicato
  3. while read con alcune osservazioni
  4. while read -uutilizzando dedicato fd, per l' elaborazione interattiva (esempio)

Per quanto riguarda la richiesta OP: in esecuzione chmodsu tutte le destinazioni elencate nel file , xargsè lo strumento indicato. Ma per alcune altre applicazioni, una piccola quantità di file, ecc ...

  1. Leggi l'intero file come argomento della riga di comando.

    Se il tuo file non è troppo grande e tutti i file hanno un buon nome (senza spazi o altri caratteri speciali come virgolette), potresti usare l' shellespansione della riga di comando . Semplicemente:

    chmod 755 $(<file.txt)

    Per una piccola quantità di file (linee), questo comando è quello più leggero.

  2. xargs è lo strumento giusto

    Per un numero maggiore di file o quasi un numero qualsiasi di righe nel file di input ...

    Per molti Binutils strumenti, come chown, chmod, rm, cp -t...

    xargs chmod 755 <file.txt

    Se hai caratteri speciali e / o molte righe in file.txt.

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

    se il comando deve essere eseguito esattamente 1 volta per voce:

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

    Questo non è necessario per questo esempio, poiché chmodaccetta più file come argomento, ma corrisponde al titolo della domanda.

    Per alcuni casi speciali, potresti persino definire la posizione dell'argomento del file nei comandi generati da xargs:

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

    Prova con seq 1 5come input

    Prova questo:

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

    Dove viene eseguito il comando una volta per riga .

  3. while read e varianti.

    Come suggerisce l'OP cat file.txt | while read in; do chmod 755 "$in"; donefunzionerà, ma ci sono 2 problemi:

    • cat |è una forcella inutile , e

    • | while ... ;donediventerà una subshell in cui l'ambiente scomparirà dopo ;done.

    Quindi questo potrebbe essere scritto meglio:

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

    Ma,

    • Potresti essere avvisato $IFSe readbandiere:

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

      In alcuni casi, potrebbe essere necessario utilizzare

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

      Per evitare problemi con nomi di file sconosciuti. E forse se riscontri problemi con UTF-8:

      while LANG=C IFS= read -r in ; do chmod 755 "$in";done <file.txt
    • Mentre lo usi STDINper la lettura file.txt, il tuo script non potrebbe essere interattivo (non puoi STDINpiù utilizzarlo ).

  4. while read -u, utilizzando dedicato fd.

    Sintassi: while read ...;done <file.txtreindirizzerà STDINa file.txt. Ciò significa che non sarai in grado di gestire il processo fino a quando non avranno terminato.

    Se si prevede di creare uno strumento interattivo , è necessario evitare l'uso STDINe l'uso di un descrittore di file alternativo .

    I descrittori di file costanti sono: 0per STDIN , 1per STDOUT e 2per STDERR . Li puoi vedere da:

    ls -l /dev/fd/

    o

    ls -l /proc/self/fd/

    Da lì, devi scegliere un numero inutilizzato, tra 0e 63(più, in effetti, a seconda dello sysctlstrumento superutente) come descrittore di file :

    Per questa demo, userò fd 7 :

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

    Quindi potresti usare in read -u 7questo modo:

    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

    Per chiudere fd/7:

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

    Nota: ho lasciato la versione colpita perché questa sintassi potrebbe essere utile quando si eseguono molti I / O con processi paralleli:

    mkfifo sshfifo
    exec 7> >(ssh -t user@host sh >sshfifo)
    exec 6<sshfifo

3
Come è xargsstato inizialmente creato per rispondere a questo tipo di necessità, alcune funzionalità, come la creazione di comandi il più a lungo possibile nell'ambiente attuale per invocare chmodin questo caso il meno possibile, ridurre le forche garantiscono efficienza. while ;do..done <$fileimplie eseguendo 1 fork per 1 file. xargspotrebbe eseguire 1 fork per migliaia di file ... in modo affidabile.
F. Hauri,

1
perché il terzo comando non funziona in un makefile? sto ricevendo "errore di sintassi vicino al token imprevisto` <'", ma l'esecuzione direttamente dalla riga di comando funziona.
Woodrow Barlow,

2
Questo sembra collegato alla sintassi specifica di Makefile. Potresti provare a invertire la riga di comando:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
F. Hauri,

@ F.Hauri per qualche motivo, tr \\n \\0 <file.txt |xargs -0 [command]è circa il 50% più veloce del metodo che hai descritto.
phil294,

Ottobre 2019, nuova modifica, aggiungi un esempio di processore file interattivo .
F. Hauri,

150

Sì.

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

In questo modo puoi evitare un catprocesso.

catfa quasi sempre male per uno scopo come questo. Puoi leggere di più sull'uso inutile di Cat.


Evitarne una cat è una buona idea, ma in questo caso il comando indicato èxargs
F. Hauri il

Quel link non sembra essere rilevante, forse il contenuto della pagina web è cambiato? Il resto della risposta è fantastico però :)
starbeamrainbowlabs

@starbeamrainbowlabs Sì. Sembra che la pagina sia stata spostata. Ho ricollegato e ora dovrebbe essere ok. Grazie :)
PP,

1
Grazie! Questo è stato utile, soprattutto quando è necessario fare qualcos'altro oltre a chiamare chmod(ovvero eseguire un comando per ogni riga nel file).
Per Lundberg,

fai attenzione alle barre rovesciate! da unix.stackexchange.com/a/7561/28160 - " read -rlegge una singola riga dall'input standard ( readsenza -rinterpretare le barre rovesciate, non lo vuoi)."
Quel ragazzo brasiliano, il

16

se hai un bel selettore (ad esempio tutti i file .txt in una directory) puoi fare:

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

bash per loop

o una tua variante:

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

Ciò che non funziona è che se ci sono spazi nella linea, l'input è diviso per spazi, non per linea.
Michael Fox,

@Michael Fox: le linee con spazi possono essere supportate cambiando il separatore. Per cambiarlo in newline, impostare la variabile d'ambiente 'IFS' prima dello script / comando. Esempio: export IFS = '$ \ n'
codesniffer

Errore di battitura nel mio ultimo commento. Dovrebbe essere: export IFS = $ '\ n'
codesniffer

14

Se sai di non avere spazi bianchi nell'input:

xargs chmod 755 < file.txt

Se potrebbero esserci spazi bianchi nei percorsi e se hai GNU xargs:

tr '\n' '\0' < file.txt | xargs -0 chmod 755

Conosco xargs, ma (purtroppo) sembra meno una soluzione affidabile di bash funzionalità integrate come while e read. Inoltre, non ho GNU xargs, ma sto usando OS X e xargs ha anche un'opzione -0 qui. Grazie per la risposta.
falco

1
@hawk No: xargsè robusto. Questo strumento è molto vecchio e il suo codice è fortemente rivisitato . Il suo obiettivo era inizialmente quello di costruire linee rispetto alle limitazioni della shell (64kchar / linea o qualcosa del genere). Ora questo strumento potrebbe funzionare con file di dimensioni molto grandi e potrebbe ridurre notevolmente il numero di fork fino al comando finale. Vedi la mia risposta e / o man xargs.
F. Hauri,

@hawk Meno di una soluzione affidabile in che modo? Se funziona in Linux, Mac / BSD e Windows (sì, i bundle GNU xargs di MSYSGIT), è affidabile come si arriva.
Camilo Martin,

1
Per quelli che continuano a trovarlo dai risultati della ricerca ... puoi installare GNU xargs su macOS usando Homebrew ( brew install findutils), e quindi invocare GNU xargs con gxargsinvece, ad es.gxargs chmod 755 < file.txt
Jase

13

Se vuoi eseguire il tuo comando in parallelo per ogni linea puoi usare GNU Parallel

parallel -a <your file> <program>

Ogni riga del file verrà passata al programma come argomento. Per impostazione predefinita, parallelesegue quanti thread contano le CPU. Ma puoi specificarlo con-j


3

Vedo che hai taggato bash, ma Perl sarebbe anche un buon modo per farlo:

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

Puoi anche applicare una regex per assicurarti di ottenere i file giusti, ad esempio per elaborare solo i file .txt:

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

Per "visualizzare in anteprima" ciò che sta accadendo, basta sostituire i backtick con doppie virgolette e anteporre print:

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

2
Perché usare i backtick? Perl ha una chmodfunzione
Glenn Jackman,

1
Che ci si vuole perl -lpe 'chmod 0755, $_' file.txt- uso -lper la funzione "auto-Chomp"
Glenn Jackman



0

So che è tardi ma comunque

Se per caso ti imbatti in file di testo salvato con Windows \r\ninvece di \n, potresti essere confuso dall'output se il tuo comando ha sth dopo aver letto la riga come argomento. Quindi rimuovere \r, ad esempio:

cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.