rm bekerja pada baris perintah tetapi tidak dalam skrip


11

Ketika saya melakukannya rm *.old.*pada baris perintah, itu dihapus dengan benar, tetapi ketika saya melakukannya di bagian berikut dari skrip saya, itu tidak rm semua *.old.*file.

Apa yang salah dalam skrip bash saya:

 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

Jawaban:


4

Jika saya mengerti apa yang Anda lakukan (hapus file apa pun dengan .oldsufiks, dan buat salinan file apa pun yang ada dengan .oldsufiks), Anda bisa menggunakan 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 0menghentikan perintah find yang mencari di subdirektori, hanya -type fmencari file biasa; -printfmembuat pesan ( %Padalah nama file yang ditemukan). The -exec cpmemanggil fungsi copy dan '{}'adalah nama file


14

Ada berbagai kemungkinan poin kegagalan dalam skrip Anda. Pertama-tama, rm *.old*akan menggunakan globbing untuk membuat daftar semua file yang cocok, dan itu bisa berurusan dengan nama file yang mengandung spasi. Namun, skrip Anda memberikan variabel ke setiap hasil glob dan melakukannya tanpa mengutip. Itu akan rusak jika nama file Anda mengandung spasi. Sebagai contoh:

$ 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'

Seperti yang Anda lihat, loop gagal untuk file dengan spasi dalam namanya. Untuk melakukannya dengan benar, Anda perlu mengutip variabel:

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

Masalah yang sama berlaku untuk hampir setiap penggunaan $idalam skrip Anda. Anda harus selalu mengutip variabel Anda .

Masalah berikutnya yang mungkin terjadi adalah Anda sepertinya mengharapkan *.old.*file yang cocok dengan ekstensi .old. Tidak. Ini cocok dengan "0 karakter atau lebih" ( *), lalu a ., lalu "lama", lalu yang lain .dan kemudian "0 atau lebih karakter lagi". Ini berarti bahwa itu tidak akan cocok dengan sesuatu seperti file.old, tetapi hanya sesuatu seperti `file.old.foo:

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

Jadi, tidak ada lawan yang cocok file.old. Bagaimanapun, skrip Anda jauh lebih kompleks daripada yang dibutuhkan. Coba yang ini sebagai gantinya:

#!/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

Perhatikan bahwa saya menambahkan -vke pernyataan rmdan cp which does the same thing as what you were doing with yourecho`.

Ini tidak sempurna karena ketika Anda menemukan, misalnya, file.oldyang akan dihapus dan, nanti, skrip akan mencoba menyalinnya dan gagal karena file tidak ada lagi. Namun, Anda belum menjelaskan apa yang sebenarnya ingin dilakukan skrip Anda, jadi saya tidak dapat memperbaikinya kecuali Anda memberi tahu kami apa yang sebenarnya ingin Anda capai.

Jika yang Anda inginkan adalah i) menghapus semua file dengan .oldekstensi dan ii) menambahkan .oldekstensi ke file yang ada yang tidak memilikinya, yang Anda butuhkan adalah:

#!/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

Saya mencoba untuk membuat cadangan file ke file.old tetapi pada saat yang sama rm file yang berakhiran .old.old atau .old.old.old atau .old.old.old dll. Pada commandline yang saya gunakan rm *.old.*yang menghapus file-file ini tetapi tidak file.old file cadangan. Saya mencoba melakukan itu dalam skrip saya. Terima kasih
Don

1
@Don harap edit pertanyaan Anda dan jelaskan ini secara lebih rinci. Sertakan contoh nama file dan apa yang Anda inginkan terjadi setelah skrip Anda dijalankan. Idealnya, masuklah ke dalam obrolan dan ping saya di sana agar kita bisa membahasnya.
terdon

8

Satu-satunya kasus rm $oldfilebisa gagal adalah ketika nama file Anda mengandung karakter IFS(spasi, tab, baris baru) atau karakter gumpal ( *, ?, []).

Jika ada karakter dari IFSshell akan melakukan pemisahan kata dan berdasarkan keberadaan ekspansi pathname karakter globbing pada ekspansi variabel.

Jadi, misalnya, jika nama file foo bar.old., variabel oldfileakan berisi foo bar.old..

Saat kamu melakukan:

rm $oldfile

shell pada awalnya membagi ekspansi oldfilepada ruang menjadi dua kata, foodan bar.old.. Jadi perintahnya menjadi:

rm foo bar.old.

yang jelas akan mengarah pada hasil yang tidak terduga. By the way, jika Anda memiliki operator globbing ( *, ?, []) dalam ekspansi, maka ekspansi pathname akan dilakukan juga.

Anda perlu mengutip variabel untuk mendapatkan hasil yang diinginkan:

rm "$oldfile"

Sekarang, tidak ada pemisahan kata atau perluasan pathname yang akan dilakukan, maka Anda harus mendapatkan hasil yang diinginkan yaitu file yang diinginkan akan dihapus. Jika ada nama file yang terjadi di awal -, maka lakukan:

rm -- "$oldfile"

Anda mungkin bertanya, mengapa kita tidak perlu mengutip variabel ketika digunakan di dalam [[, alasannya [[adalah bashkata kunci dan itu menangani ekspansi variabel secara internal menjaga ekspansi literal.


Sekarang, beberapa poin:

  • Anda harus mengarahkan ulang STDERR ( exec 2>errorfile) sebelum rmperintah [[ -s errorfile ]]tes dinyatakan akan memberikan positif palsu

  • Anda telah menggunakan [ -s $errorfile ], Anda menggunakan ekspansi variabel $errorfile, yang akan diberikan NUL, errorfilevariabel tidak didefinisikan di mana pun. Mungkin maksud Anda, hanya [ -s errorfile ], berdasarkan pengalihan STDERR

  • Jika variabel errorfiledidefinisikan, saat menggunakan [ -s $errorfile ], itu lagi akan tersedak kasus IFSdan globbing yang disebutkan di atas karena tidak seperti [[, [tidak ditangani secara internal olehbash

  • Di bagian akhir skrip, Anda mencoba cpfile yang sudah dihapus (lagi tanpa mengutip variabel), ini tidak masuk akal, Anda harus memeriksa chuck itu dan membuat koreksi yang diperlukan berdasarkan target Anda.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.