คุณรันคำสั่งสำหรับแต่ละบรรทัดของไฟล์ได้อย่างไร


161

ตัวอย่างเช่นตอนนี้ฉันใช้สิ่งต่อไปนี้เพื่อเปลี่ยนไฟล์ที่มีพา ธ Unix ที่ฉันเขียนไปเป็นไฟล์:

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

มีวิธีที่สง่างามและปลอดภัยกว่านี้ไหม?

คำตอบ:


127

อ่านไฟล์ทีละบรรทัดและดำเนินการคำสั่ง: 4 คำตอบ

นี่เป็นเพราะมีเพียง 1 คำตอบเท่านั้น ...

  1. shell การขยายบรรทัดคำสั่ง
  2. xargs เครื่องมือเฉพาะ
  3. while read ด้วยข้อสังเกตบางอย่าง
  4. while read -uใช้เฉพาะfdสำหรับการประมวลผลแบบโต้ตอบ (ตัวอย่าง)

เกี่ยวกับคำขอ OP นี้: การทำงานchmodกับเป้าหมายทั้งหมดที่ระบุไว้ในแฟ้ม , xargsเป็นเครื่องมือที่ระบุ แต่สำหรับแอปพลิเคชั่นอื่น ๆ ไฟล์จำนวนเล็กน้อย ฯลฯ ...

  1. อ่านไฟล์ทั้งหมดเป็นอาร์กิวเมนต์บรรทัดคำสั่ง

    หากไฟล์ของคุณไม่ใหญ่เกินไปและไฟล์ทั้งหมดมีชื่อที่ดี (ไม่มีช่องว่างหรือตัวอักษรพิเศษอื่น ๆ เช่นเครื่องหมายคำพูด) คุณสามารถใช้shellการขยายบรรทัดคำสั่งได้ เพียง:

    chmod 755 $(<file.txt)

    สำหรับไฟล์จำนวนเล็กน้อย (บรรทัด) คำสั่งนี้จะเบากว่า

  2. xargs เป็นเครื่องมือที่เหมาะสม

    สำหรับไฟล์ที่มีจำนวนมากขึ้นหรือเกือบทุกบรรทัดในไฟล์อินพุตของคุณ ...

    สำหรับหลาย ๆ คนbinutilsเครื่องมือเช่นchown, chmod, rm, cp -t...

    xargs chmod 755 <file.txt

    ถ้าคุณมีตัวอักษรพิเศษและ / file.txtหรือจำนวนมากของสายใน

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

    หากคำสั่งของคุณจำเป็นต้องเรียกใช้ 1 ครั้งโดยรายการ:

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

    สิ่งนี้ไม่จำเป็นสำหรับตัวอย่างนี้เนื่องจากchmodยอมรับหลายไฟล์เป็นอาร์กิวเมนต์ แต่ตรงกับหัวเรื่องของคำถาม

    สำหรับกรณีพิเศษบางอย่างคุณสามารถกำหนดตำแหน่งของอาร์กิวเมนต์ไฟล์ในคำสั่งที่สร้างโดยxargs:

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

    ทดสอบด้วยseq 1 5อินพุต

    ลองสิ่งนี้:

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

    ที่ไหน Commande จะทำครั้งเดียวต่อบรรทัด

  3. while read และตัวแปร

    ตามที่ OP แนะนำcat file.txt | while read in; do chmod 755 "$in"; doneจะใช้งานได้ แต่มี 2 ประเด็น:

    • cat |เป็นทางแยกที่ไร้ประโยชน์และ

    • | while ... ;doneจะกลายเป็นsubshell;doneที่สภาพแวดล้อมจะหายไปเช่นกันหลังจากที่

    ดังนั้นนี่อาจเป็นลายลักษณ์อักษรที่ดีกว่า:

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

    แต่,

    • คุณอาจได้รับคำเตือนเกี่ยวกับ$IFSและreadธง:

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

      ในบางกรณีคุณอาจจำเป็นต้องใช้

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

      สำหรับการหลีกเลี่ยงปัญหากับชื่อไฟล์ที่หลากหลาย และถ้าคุณเข้ารหัสปัญหาด้วยUTF-8:

      while LANG=C IFS= read -r in ; do chmod 755 "$in";done <file.txt
    • ในขณะที่คุณใช้STDINสำหรับการอ่านfile.txtสคริปต์ของคุณไม่สามารถโต้ตอบได้ (คุณไม่สามารถใช้STDINอีกต่อไป)

  4. while read -ufdโดยใช้ทุ่มเท

    ไวยากรณ์: while read ...;done <file.txtจะเปลี่ยนเส้นทางไปยังSTDIN file.txtหมายความว่าคุณจะไม่สามารถจัดการกับกระบวนการจนกว่าจะเสร็จสิ้น

    หากคุณวางแผนที่จะสร้างเครื่องมือแบบโต้ตอบคุณต้องหลีกเลี่ยงการใช้STDINและใช้ตัวอธิบายไฟล์อื่นๆ

    คงอธิบายไฟล์คือ: 0สำหรับSTDIN , 1สำหรับSTDOUTและ2สำหรับSTDERR คุณสามารถดูได้โดย:

    ls -l /dev/fd/

    หรือ

    ls -l /proc/self/fd/

    จากตรงนั้นคุณต้องเลือกหมายเลขที่ไม่ได้ใช้ระหว่าง0และ63(ขึ้นอยู่กับความเป็นจริงโดยขึ้นอยู่กับsysctlเครื่องมือ superuser) เป็นตัวบอกไฟล์ :

    สำหรับการสาธิตนี้ฉันจะใช้fd 7 :

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

    จากนั้นคุณสามารถใช้read -u 7วิธีนี้:

    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

    เพื่อปิดfd/7:

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

    หมายเหตุ: ฉันปล่อยรุ่นที่มีขีดกลางเนื่องจากไวยากรณ์นี้อาจเป็นประโยชน์เมื่อทำ I / O จำนวนมากด้วยกระบวนการที่คล้ายคลึงกัน:

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

3
ในฐานะที่xargsเป็น Initialy สร้างสำหรับการตอบรับชนิดของความต้องการคุณสมบัติบางอย่างนี้เช่นการสร้างคำสั่งเป็นเวลานานที่สุดในสภาพแวดล้อมปัจจุบันสำหรับการเรียกchmodในกรณีนี้เป็นไปได้น้อยลงเช่นลดส้อมให้มั่นใจ efficience while ;do..done <$fileimplie กำลังเรียกใช้ 1 fork สำหรับ 1 ไฟล์ xargsสามารถรัน 1 fork สำหรับไฟล์พันไฟล์ ... ในลักษณะที่เชื่อถือได้
F. Hauri

1
ทำไมคำสั่งที่สามไม่ทำงานใน makefile ฉันได้รับ "ข้อผิดพลาดทางไวยากรณ์ใกล้กับโทเค็นที่ไม่คาดคิด` <'"แต่การดำเนินการโดยตรงจากบรรทัดคำสั่งใช้งานได้
Woodrow Barlow

2
ดูเหมือนว่าลิงก์กับไวยากรณ์เฉพาะ Makefile คุณสามารถลองย้อนกลับบรรทัดคำสั่ง:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
Hauri F.

@ F.Hauri ด้วยเหตุผลบางอย่างtr \\n \\0 <file.txt |xargs -0 [command]เร็วกว่าวิธีที่คุณอธิบายประมาณ 50%
phil294

ตุลาคม 2562 แก้ไขใหม่เพิ่มตัวอย่างประมวลผลไฟล์แบบโต้ตอบ
F. Hauri

150

ใช่.

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

วิธีนี้คุณสามารถหลีกเลี่ยงcatกระบวนการ

catเกือบจะไม่ดีเสมอไปสำหรับวัตถุประสงค์เช่นนี้ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับการใช้แมวที่ไร้ประโยชน์


หลีกเลี่ยงการอย่างใดอย่างหนึ่ง catเป็นความคิดที่ดี แต่ในกรณีนี้คำสั่งที่ระบุเป็นxargs
เอฟ HAURI

ลิงค์นั้นดูเหมือนจะไม่เกี่ยวข้องบางทีเนื้อหาของหน้าเว็บมีการเปลี่ยนแปลงหรือไม่ คำตอบที่เหลือนั้นยอดเยี่ยมมาก :)
starbeamrainbowlabs

@ starbeamrainbowlabs ใช่ ดูเหมือนว่าหน้าเว็บจะถูกย้าย ฉันมีการเชื่อมโยงใหม่และควรจะตกลงตอนนี้ ขอบคุณ :)
PP

1
ขอบคุณ! สิ่งนี้มีประโยชน์โดยเฉพาะเมื่อคุณต้องการทำสิ่งอื่นนอกเหนือจากการโทรchmod(เช่นเรียกใช้คำสั่งเดียวสำหรับแต่ละบรรทัดในไฟล์)
ต่อ Lundberg

ระวังด้วยแบ็กสแลช! จากunix.stackexchange.com/a/7561/28160 - " read -rอ่านบรรทัดเดียวจากอินพุตมาตรฐาน ( readโดยไม่-rแปลเครื่องหมายแบ็กสแลชคุณไม่ต้องการ)"
Guy ชาวบราซิลนั่น

16

หากคุณมีตัวเลือกที่ดี (เช่นไฟล์. txt ทั้งหมดใน dir) คุณสามารถทำได้:

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

ทุบตีสำหรับวง

หรือตัวแปรของคุณ:

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

สิ่งที่ใช้ไม่ได้คือถ้ามีช่องว่างในบรรทัดอินพุตจะถูกแบ่งตามช่องว่างไม่ใช่ตามบรรทัด
Michael Fox

@Michael Fox: บรรทัดที่มีช่องว่างสามารถรองรับได้โดยการเปลี่ยนตัวคั่น หากต้องการเปลี่ยนเป็นบรรทัดใหม่ให้ตั้งค่าตัวแปรสภาพแวดล้อม 'IFS' ก่อนสคริปต์ / คำสั่ง ตัวอย่างเช่น export IFS = '$ \ n'
codesniffer

พิมพ์ผิดในความคิดเห็นล่าสุดของฉัน ควรเป็น: export IFS = $ '\ n'
codesniffer

14

หากคุณรู้ว่าคุณไม่มีช่องว่างในอินพุต:

xargs chmod 755 < file.txt

หากอาจมีช่องว่างในเส้นทางและถ้าคุณมี GNU xargs:

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

ฉันรู้เกี่ยวกับ xargs แต่ (น่าเศร้า) ดูเหมือนว่าวิธีการแก้ปัญหาที่เชื่อถือได้น้อยกว่าการทุบตีคุณสมบัติในตัวเช่นขณะและอ่าน นอกจากนี้ฉันไม่มี GNU xargs แต่ฉันใช้ OS X และ xargs ก็มีตัวเลือก -0 ที่นี่ ขอบคุณสำหรับคำตอบ.
เหยี่ยว

1
@ Hawk No: xargsแข็งแกร่ง เครื่องมือนี้เก่ามากและรหัสของเขาขอมาเยือน เป้าหมายของเขาคือการสร้างบรรทัดในส่วนที่เกี่ยวกับข้อ จำกัด ของเชลล์ (64kchar / บรรทัดหรือบางอย่าง) ตอนนี้เครื่องมือนี้สามารถทำงานกับไฟล์ที่มีขนาดใหญ่มากและอาจลดจำนวนส้อมลงเป็นคำสั่งสุดท้าย ดูคำตอบและ / หรือของman xargsฉัน
F. Hauri

@ ฮอว์กน้อยวิธีการแก้ปัญหาที่เชื่อถือได้ในทางใด? ถ้ามันใช้งานได้ใน Linux, Mac / BSD และ Windows (ใช่แล้วบันเดิล GNU xargs ของ MSYSGIT) แสดงว่ามันน่าเชื่อถือเหมือนที่ได้รับ
Camilo Martin

1
สำหรับผู้ที่ยังคงค้นหาสิ่งนี้จากผลการค้นหา ... คุณสามารถติดตั้ง GNU xargs บน macOS โดยใช้ Homebrew ( brew install findutils) จากนั้นเรียกใช้ GNU xargs ด้วยgxargsแทนเช่นgxargs chmod 755 < file.txt
Jase

13

หากคุณต้องการรันคำสั่งแบบขนานสำหรับแต่ละบรรทัดคุณสามารถใช้GNU Parallel ได้

parallel -a <your file> <program>

แต่ละบรรทัดของไฟล์ของคุณจะถูกส่งผ่านไปยังโปรแกรมเป็นอาร์กิวเมนต์ โดยค่าเริ่มต้นparallelจะรันเธรดให้มากที่สุดเท่าที่ CPU ของคุณจะนับ แต่คุณสามารถระบุด้วย-j


3

ฉันเห็นว่าคุณติดแท็ก bash แต่ Perl ก็จะเป็นวิธีที่ดีในการทำเช่นนี้:

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

คุณสามารถใช้ regex เพื่อให้แน่ใจว่าคุณได้รับไฟล์ที่ถูกต้องเช่นกับการประมวลผลไฟล์. txt เท่านั้น:

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

หากต้องการ "ดูตัวอย่าง" สิ่งที่เกิดขึ้นเพียงแทนที่ backticks ด้วยเครื่องหมายคำพูดคู่และเพิ่มprint:

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

2
ทำไมต้องใช้ backticks Perl มีchmodฟังก์ชั่น
เกล็นแจ็

1
คุณต้องการperl -lpe 'chmod 0755, $_' file.txt- ใช้-lสำหรับคุณสมบัติ "auto-chomp"
glenn jackman

1

นอกจากนี้คุณยังสามารถใช้ AWK ซึ่งสามารถเพิ่มความยืดหยุ่นในการจัดการไฟล์

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

หากไฟล์ของคุณมีตัวคั่นฟิลด์เช่น:

field1, field2, Field3

เพื่อรับเฉพาะฟิลด์แรกที่คุณทำ

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

คุณสามารถตรวจสอบรายละเอียดเพิ่มเติมเกี่ยวกับเอกสาร GNU ได้ที่ https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple


0

ตรรกะนี้ใช้กับวัตถุประสงค์อื่น ๆ อีกมากมาย และวิธีการอ่าน. sh_history ของผู้ใช้แต่ละคนจาก / home / filesystem? เกิดอะไรขึ้นถ้ามีหลายพันคน?

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

นี่คือสคริปต์https://github.com/imvieira/SysAdmin_DevOps_Scripts/blob/master/get_and_run.sh


0

ฉันรู้ว่ามันสาย แต่ก็ยัง

หากมีโอกาสที่คุณพบไฟล์ข้อความที่บันทึกด้วย windows \r\nแทนที่จะเป็น\nคุณอาจสับสนโดยเอาต์พุตถ้าคำสั่งของคุณมี sth หลังจากอ่านบรรทัดเป็นอาร์กิวเมนต์ ดังนั้นจะลบ\rตัวอย่างเช่น:

cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.