Bagaimana Anda menjalankan perintah untuk setiap baris file?

161

Misalnya, sekarang saya menggunakan yang berikut untuk mengubah beberapa file yang jalur Unix yang saya tulis ke file:

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

Apakah ada cara yang lebih elegan, lebih aman?

elang
sumber

Jawaban:

127

Baca file baris demi baris dan jalankan perintah: 4 jawaban

Ini karena tidak hanya ada 1 jawaban ...

  1. shell ekspansi baris perintah
  2. xargs alat khusus
  3. while read dengan beberapa komentar
  4. while read -umenggunakan dedicated fd, untuk pemrosesan interaktif (sampel)

Mengenai permintaan OP: berjalan chmodpada semua target yang tercantum dalam file , xargsadalah alat yang ditunjukkan. Tetapi untuk beberapa aplikasi lain, sejumlah kecil file, dll ...

  1. Baca seluruh file sebagai argumen baris perintah.

    Jika file Anda tidak terlalu besar dan semua file diberi nama dengan baik (tanpa spasi atau karakter khusus lain seperti tanda kutip), Anda dapat menggunakan shellekspansi baris perintah . Secara sederhana:

    chmod 755 $(<file.txt)

    Untuk sejumlah kecil file (baris), perintah ini adalah yang lebih ringan.

  2. xargs adalah alat yang tepat

    Untuk jumlah file yang lebih besar, atau hampir semua jumlah baris di file input Anda ...

    Bagi banyak binutils alat, seperti chown, chmod, rm, cp -t...

    xargs chmod 755 <file.txt

    Jika Anda memiliki karakter khusus dan / atau banyak baris file.txt.

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

    jika perintah Anda harus dijalankan tepat 1 kali dengan entri:

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

    Ini tidak diperlukan untuk sampel ini, karena chmodmenerima beberapa file sebagai argumen, tetapi ini sesuai dengan judul pertanyaan.

    Untuk beberapa kasus khusus, Anda bahkan dapat menentukan lokasi argumen file dalam perintah yang dihasilkan oleh xargs:

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

    Uji dengan seq 1 5sebagai input

    Coba ini:

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

    Di mana perintah dilakukan sekali per baris .

  3. while read dan varian.

    Seperti yang disarankan OP cat file.txt | while read in; do chmod 755 "$in"; doneakan berfungsi, tetapi ada 2 masalah:

    • cat |adalah garpu yang tidak berguna , dan

    • | while ... ;doneakan menjadi subkulit di mana lingkungan akan menghilang setelahnya ;done.

    Jadi ini bisa ditulis lebih baik:

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

    Tapi,

    • Anda mungkin diperingatkan $IFSdan readditandai:

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

      Dalam beberapa kasus, Anda mungkin perlu menggunakan

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

      Untuk menghindari masalah dengan nama file stranges. Dan mungkin jika Anda mengalami masalah dengan UTF-8:

      while LANG=C IFS= read -r in ; do chmod 755 "$in";done <file.txt
    • Saat Anda menggunakan STDINuntuk membaca file.txt, skrip Anda tidak dapat interaktif (Anda tidak dapat menggunakan STDINlagi).

  4. while read -u, menggunakan dedicated fd.

    Sintaks: while read ...;done <file.txtakan dialihkan STDINke file.txt. Itu berarti, Anda tidak akan dapat menangani proses, sampai mereka selesai.

    Jika Anda berencana membuat alat interaktif , Anda harus menghindari penggunaan STDINdan menggunakan beberapa deskriptor file alternatif .

    Deskriptor file Konstanta adalah: 0untuk STDIN , 1untuk STDOUT dan 2untuk STDERR . Anda bisa melihatnya dengan:

    ls -l /dev/fd/

    atau

    ls -l /proc/self/fd/

    Dari sana, Anda harus memilih nomor yang tidak digunakan, antara 0dan 63(lebih banyak, tergantung pada sysctlalat superuser) sebagai deskriptor file :

    Untuk demo ini, saya akan menggunakan fd 7 :

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

    Maka Anda bisa menggunakan read -u 7cara ini:

    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

    Untuk menutup fd/7:

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

    Nota: Saya membiarkan versi striked karena sintaks ini bisa berguna, ketika melakukan banyak I / O dengan proses paralel:

    mkfifo sshfifo
    exec 7> >(ssh -t user@host sh >sshfifo)
    exec 6<sshfifo
    
F. Hauri
sumber
3
Seperti xargsawalnya dibangun untuk menjawab kebutuhan semacam ini, beberapa fitur, seperti membangun perintah selama mungkin di lingkungan saat ini untuk memohon chmoddalam kasus ini kurang mungkin, mengurangi garpu memastikan efisiensi. while ;do..done <$filejalankan 1 garpu untuk 1 file. xargsdapat menjalankan 1 garpu untuk ribuan file ... dengan cara yang dapat diandalkan.
F. Hauri
1
mengapa perintah ketiga tidak berfungsi di makefile? saya mendapatkan "kesalahan sintaks dekat token yang tidak terduga` <'", tetapi mengeksekusi langsung dari command line berfungsi.
Woodrow Barlow
2
Tampaknya ini terkait dengan sintaksis Makefile tertentu. Anda bisa mencoba membalikkan baris perintah:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
F. Hauri
@ F. Hauri untuk beberapa alasan, tr \\n \\0 <file.txt |xargs -0 [command]sekitar 50% lebih cepat dari metode yang Anda gambarkan.
phil294
Oktober 2019, hasil edit baru, tambahkan sampel prosesor file interaktif .
F. Hauri
150

Iya.

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

Dengan cara ini Anda dapat menghindari suatu catproses.

cathampir selalu buruk untuk tujuan seperti ini. Anda dapat membaca lebih lanjut tentang Penggunaan Cat yang Tidak Berguna.

PP
sumber
Hindari satu cat adalah ide yang baik, tetapi dalam kasus ini, yang perintah yang ditunjukkan adalahxargs
F. Hauri
Tautan itu tampaknya tidak relevan, mungkin konten halaman web telah berubah? Sisa dari jawabannya sungguh luar biasa :)
starbeamrainbowlabs
@ starbeamrainbowlabs Ya. Sepertinya halaman telah dipindahkan. Saya telah ditautkan kembali dan seharusnya baik-baik saja sekarang. Terima kasih :)
PP
1
Terima kasih! Ini sangat membantu, terutama ketika Anda perlu melakukan sesuatu selain memanggil chmod(yaitu benar-benar menjalankan satu perintah untuk setiap baris dalam file).
Per Lundberg
hati-hati dengan garis miring terbalik! dari unix.stackexchange.com/a/7561/28160 - " read -rmembaca satu baris dari input standar ( readtanpa -rmenginterpretasikan backslash, Anda tidak menginginkan itu)."
Orang Brasil itu
16

jika Anda memiliki pemilih yang bagus (misalnya semua file .txt dalam direktori) yang dapat Anda lakukan:

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

bash untuk loop

atau varian Anda:

while read line; do chmod 755 "$line"; done <file.txt
d.raev
sumber
Yang tidak berfungsi adalah jika ada spasi di baris, input dibagi dengan spasi, bukan baris.
Michael Fox
@Michael Fox: Garis dengan spasi dapat didukung dengan mengubah pemisah. Untuk mengubahnya ke baris baru, setel variabel lingkungan 'IFS' sebelum script / perintah. Mis: ekspor IFS = '$ \ n'
codesniffer
Mengetik di komentar terakhir saya. Seharusnya: export IFS = $ '\ n'
codesniffer
14

Jika Anda tahu Anda tidak memiliki spasi kosong di input:

xargs chmod 755 < file.txt

Jika mungkin ada spasi putih di jalur, dan jika Anda memiliki GNU xargs:

tr '\n' '\0' < file.txt | xargs -0 chmod 755
glenn jackman
sumber
Saya tahu tentang xargs, tetapi (sayangnya) sepertinya ini bukan solusi yang dapat diandalkan daripada fitur built-in bash seperti saat dan membaca. Juga, saya tidak punya GNU xargs, tetapi saya menggunakan OS X dan xargs juga memiliki opsi -0 di sini. Terima kasih atas jawabannya.
hawk
1
@hawk No: xargskuat. Alat ini sangat tua dan kodenya sangat ditinjau kembali . Tujuannya semula adalah untuk membangun garis sehubungan dengan batasan shell (64kchar / line atau semacamnya). Sekarang alat ini dapat bekerja dengan file yang sangat besar dan dapat mengurangi banyak jumlah garpu ke perintah akhir. Lihat jawaban saya dan / atau man xargs.
F. Hauri
@ Hawk Kurang dari solusi yang dapat diandalkan di mana? Jika itu bekerja di Linux, Mac / BSD dan Windows (ya, bundel MSUsGIT GNU xargs), maka itu dapat diandalkan seperti yang didapat.
Camilo Martin
1
Bagi mereka yang datang masih menemukan ini dari hasil pencarian ... Anda dapat menginstal GNU xargs pada macOS dengan menggunakan Homebrew ( brew install findutils), dan kemudian memanggil GNU xargs dengan gxargssebaliknya, misalnyagxargs chmod 755 < file.txt
Jase
13

Jika Anda ingin menjalankan perintah secara paralel untuk setiap baris, Anda dapat menggunakan GNU Parallel

parallel -a <your file> <program>

Setiap baris file Anda akan diteruskan ke program sebagai argumen. Secara default parallelberjalan sebanyak thread yang dihitung CPU Anda. Tapi Anda bisa menentukannya dengan-j

janisz
sumber
3

Saya melihat bahwa Anda menandai bash, tetapi Perl juga merupakan cara yang baik untuk melakukan ini:

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

Anda juga bisa menerapkan regex untuk memastikan Anda mendapatkan file yang tepat, misalnya hanya memproses file .txt:

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

Untuk "melihat" apa yang terjadi, cukup ganti backtick dengan tanda kutip ganda dan tambahkan print:

perl -p -e 'if(/\.txt$/) print "chmod 755 $_"' file.txt
1.618
sumber
2
Mengapa menggunakan backticks? Perl memiliki chmodfungsi
glenn jackman
1
Anda ingin perl -lpe 'chmod 0755, $_' file.txt- gunakan -luntuk fitur "auto-chomp"
glenn jackman
1

Anda juga dapat menggunakan AWK yang dapat memberi Anda lebih banyak fleksibilitas untuk menangani file

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

jika file Anda memiliki pemisah bidang seperti:

field1, field2, field3

Untuk mendapatkan hanya bidang pertama yang Anda lakukan

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

Anda dapat memeriksa detail lebih lanjut tentang Dokumentasi GNU https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple

brunocrt
sumber
0

Logikanya berlaku untuk banyak tujuan lain. Dan bagaimana cara membaca .sh_history setiap pengguna dari / home / filesystem? Bagaimana jika jumlahnya ribuan?

#!/bin/ksh
last |head -10|awk '{print $1}'|
 while IFS= read -r line
 do
su - "$line" -c 'tail .sh_history'
 done

Ini adalah skrip https://github.com/imvieira/SysAdmin_DevOps_Scripts/blob/master/get_and_run.sh

BladeMight
sumber
0

Aku tahu ini sudah terlambat tapi tetap saja

Jika kebetulan Anda lari ke file teks yang disimpan dengan windows \r\nbukan \n, Anda mungkin bingung dengan output jika perintah Anda telah sth setelah membaca baris sebagai argumen. Jadi jangan hapus \r, misalnya:

cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
EP
sumber