rm ทำงานบนบรรทัดรับคำสั่ง แต่ไม่อยู่ในสคริปต์


11

เมื่อฉันทำrm *.old.*ในบรรทัดคำสั่งจะลบอย่างถูกต้อง แต่เมื่อฉันทำในส่วนต่อไปของสคริปต์ของฉันมันไม่ได้ rm *.old.*ไฟล์ทั้งหมด

มีอะไรผิดปกติในสคริปต์ทุบตีของฉัน:

 for i in ./*; do
    if [[ -f $i ]];  then

        if [[ $i  ==  *.old.* ]]; then
                oldfile=$i
                echo "this file is to be removed: $oldfile"
                rm $oldfile
                exec 2>errorfile
            if [ -s $errorfile ]
            then
                echo "rm failed"
            else
                echo "removed $oldfile!"
            fi
        else
            echo "file with old extension  does not exist"
        fi

        orig=$i
        dest=$i.old
        cp $orig $dest
        echo "Copied $i"

    else
        echo "${i} is not a file"
    fi 
done

คำตอบ:


4

ถ้าฉันเข้าใจสิ่งที่คุณกำลังทำอยู่ (ลบไฟล์ใด ๆ ที่มี.oldคำต่อท้ายและทำสำเนาไฟล์ที่มีอยู่ด้วย.oldคำต่อท้าย) คุณสามารถใช้ find แทน:

#!/bin/sh

find . -maxdepth 1 -name \*.old -type f -printf "deleting %P\n" -delete
find . -maxdepth 1 -type f -printf "copying %P to %P.old\n" -exec cp '{}' '{}.old' \;

-maxdepth 0หยุดคำสั่ง find ที่ค้นหาในไดเร็กทอรีย่อย-type fค้นหาไฟล์ปกติเท่านั้น -printfสร้างข้อความ ( %Pพบชื่อไฟล์) -exec cpเรียกฟังก์ชั่นการคัดลอกและ'{}'เป็นชื่อไฟล์


14

สคริปต์ของคุณมีจุดล้มเหลวหลายจุด ก่อนอื่นrm *.old*จะใช้globbingเพื่อสร้างรายการไฟล์ที่ตรงกันทั้งหมดและที่สามารถจัดการกับชื่อไฟล์ที่มีช่องว่าง อย่างไรก็ตามสคริปต์ของคุณจะกำหนดตัวแปรให้กับผลลัพธ์แต่ละตัวของ glob และดำเนินการโดยไม่ต้องอ้างถึง ซึ่งจะแตกถ้าชื่อไฟล์ของคุณมีช่องว่าง ตัวอย่างเช่น:

$ ls
'file name with spaces.old.txt'  file.old.txt
$ rm *.old.*   ## works: both files are deleted

$ touch "file.old.txt" "file name with spaces.old.txt"
$ for i in ./*; do oldfile=$i; rm -v $oldfile; done
rm: cannot remove './file': No such file or directory
rm: cannot remove 'name': No such file or directory
rm: cannot remove 'with': No such file or directory
rm: cannot remove 'spaces.old.txt': No such file or directory
removed './file.old.txt'

อย่างที่คุณเห็นลูปล้มเหลวสำหรับไฟล์ที่มีช่องว่างในชื่อ ในการทำอย่างถูกต้องคุณจะต้องอ้างอิงตัวแปร:

$ for i in ./*; do oldfile="$i"; rm -v "$oldfile"; done
removed './file name with spaces.old.txt'
removed './file.old.txt'

ปัญหาเดียวกันนี้มีผลกับการใช้งาน$iสคริปต์ของคุณ คุณควรเสมอพูดตัวแปรของคุณ

ปัญหาที่เป็นไปได้ต่อไปคือการที่คุณดูเหมือนจะคาดหวังว่าตรงกับไฟล์ที่มีนามสกุล*.old.* .oldมันไม่ได้ มันตรงกับ "0 หรือมากกว่าตัวอักษร" ( *) จากนั้น a .และจากนั้น "เก่า" แล้วอีกคนหนึ่ง.และจากนั้น "0 หรือมากกว่าตัวละครอีกครั้ง" ซึ่งหมายความว่ามันจะไม่ตรงกับสิ่งที่ชอบfile.oldแต่เฉพาะบางอย่างเช่น `file.old.foo:

$ ls
file.old  file.old.foo
$ for i in *; do if [[ "$i" == *.old.* ]]; then echo $i; fi; done
file.old.foo     

file.oldดังนั้นไม่ตรงกับศัตรู ไม่ว่าในกรณีใดสคริปต์ของคุณจะซับซ้อนเกินกว่าที่จำเป็น ลองอันนี้แทน:

#!/bin/bash

for i in *; do
    if [[ -f "$i" ]];  then
        if [[ "$i"  ==  *.old ]]; then
            rm -v "$i" || echo "rm failed for $i"
        else
            echo "$i doesn't have an .old extension"
        fi
        cp -v "$i" "$i".old
    else
        echo "$i is not a file"
    fi 
done

โปรดทราบว่าฉันเพิ่มลง-vในคำสั่งrmและ cp which does the same thing as what you were doing with yourecho`

สิ่งนี้ไม่สมบูรณ์ตั้งแต่เมื่อคุณค้นหาตัวอย่างเช่นfile.oldที่จะถูกลบและในภายหลังสคริปต์จะพยายามคัดลอกและล้มเหลวเนื่องจากไฟล์ไม่มีอยู่อีกต่อไป อย่างไรก็ตามคุณยังไม่ได้อธิบายว่าสคริปต์ของคุณพยายามทำอะไรฉันไม่สามารถแก้ไขได้ถ้าคุณไม่บอกเราว่าคุณพยายามทำอะไรให้สำเร็จ

หากสิ่งที่คุณต้องการคือ i) ลบไฟล์ทั้งหมดที่มี.oldนามสกุลและ ii) เพิ่ม.oldส่วนขยายไปยังไฟล์ที่มีอยู่ที่ไม่มีมันทั้งหมดที่คุณต้องการคือ:

#!/bin/bash

for i in *.old; do
    if [[ -f "$i" ]]; then
        rm -v "$i" || echo "rm failed for $i"
    else
        echo "$i is not a file"
    fi 
done
## All the ,old files have been removed at this point
## copy the rest
for i in *; do
    if [[ -f "$i" ]]; then
        ## the -v makes cp report copied files
        cp -v "$i" "$i".old
    fi
done

ฉันพยายามสำรองไฟล์ไปที่ file.old แต่ในเวลาเดียวกัน rm ไฟล์ใด ๆ ที่ลงท้ายด้วย. old.old หรือ. old.old.old หรือ. old.old.old เป็นต้นใน commandline ฉันใช้rm *.old.*ซึ่งลบไฟล์เหล่านี้ แต่ไม่ใช่ไฟล์ backup.old ฉันกำลังพยายามทำสิ่งนั้นในสคริปต์ของฉัน ขอบคุณ
Don

1
@ Don โปรดแก้ไขคำถามของคุณและอธิบายในรายละเอียดเพิ่มเติม รวมชื่อไฟล์ตัวอย่างและสิ่งที่คุณต้องการให้เกิดขึ้นหลังจากรันสคริปต์ของคุณ นึกคิดเข้ามาแชทและ ping ฉันที่นั่นเพื่อให้เราสามารถพูดคุยได้
terdon

8

กรณีเดียวที่rm $oldfileอาจล้มเหลวเมื่อชื่อไฟล์ของคุณมีตัวอักษรใด ๆIFS(พื้นที่แท็บขึ้นบรรทัดใหม่) หรือตัวอักษรใด ๆ glob ( *, ?, [])

หากตัวอักษรใด ๆ ของIFSมีอยู่เชลล์จะดำเนินการแยกคำและขึ้นอยู่กับการปรากฏตัวของการขยายชื่อพา ธ ตัวอักษรแบบกลมบนการขยายตัวแปร

ดังนั้นสำหรับตัวอย่างเช่นถ้าชื่อไฟล์เป็นfoo bar.old.ตัวแปรจะมีoldfilefoo bar.old.

เมื่อคุณทำ:

rm $oldfile

เชลล์ในตอนแรกแยกการขยายตัวของoldfileพื้นที่ในสองคำfooและbar.old.. ดังนั้นคำสั่งจะกลายเป็น:

rm foo bar.old.

ซึ่งจะนำไปสู่ผลลัพธ์ที่ไม่คาดคิดอย่างชัดเจน โดยวิธีการที่ถ้าคุณมีผู้ประกอบการ globbing ใด ๆ ( *, ?, []) ในการขยายตัวแล้วขยายตัวชื่อพา ธ จะทำเกินไป

คุณต้องพูดตัวแปรเพื่อให้ได้ผลลัพธ์ที่ต้องการ:

rm "$oldfile"

ตอนนี้จะไม่มีการแบ่งคำหรือการขยายชื่อพา ธ ดังนั้นคุณควรได้รับผลลัพธ์ที่ต้องการนั่นคือไฟล์ที่ต้องการจะถูกลบออก หากชื่อไฟล์ใด ๆ เริ่มต้นด้วย-ให้ทำดังนี้

rm -- "$oldfile"

คุณอาจถามว่าทำไมเราไม่จำเป็นต้องอ้างอิงตัวแปรเมื่อใช้ภายใน[[เหตุผลที่[[เป็นbashคำหลักและจัดการกับการขยายตัวของตัวแปรภายในเพื่อรักษาตัวอักษรการขยายตัว


ตอนนี้สองสามคะแนน:

  • คุณควรเปลี่ยนเส้นทาง STDERR ( exec 2>errorfile) ก่อนrmคำสั่งมิฉะนั้น[[ -s errorfile ]]การทดสอบจะให้ผลบวกเป็นเท็จ

  • คุณใช้[ -s $errorfile ]แล้วคุณกำลังใช้การขยายตัวของตัวแปร$errorfileซึ่งจะเป็น NUL เนื่องจากerrorfileไม่ได้กำหนดตัวแปรไว้ที่ใดก็ได้ บางทีคุณอาจหมายถึงเพียงแค่[ -s errorfile ]ขึ้นอยู่กับการเปลี่ยนเส้นทาง STDERR

  • หากerrorfileมีการกำหนดตัวแปรขณะใช้[ -s $errorfile ]มันจะทำให้หายใจไม่ออกในกรณีที่กล่าวถึงข้างต้นIFSและทำให้โค้งเป็นวงกลมเพราะไม่เหมือน[[กัน[ไม่ได้จัดการภายในโดยbash

  • ในส่วนหลังของสคริปต์คุณกำลังพยายามcpไฟล์ที่ถูกลบไปแล้ว (อีกครั้งโดยไม่ต้องอ้างอิงตัวแปร) ซึ่งไม่สมเหตุสมผลคุณควรตรวจสอบการเชยและทำการแก้ไขที่จำเป็นตามเป้าหมายของคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.