rm hoạt động trên dòng lệnh nhưng không có trong script


11

Khi tôi thực hiện rm *.old.*trên dòng lệnh, nó sẽ loại bỏ một cách chính xác, nhưng khi tôi thực hiện nó trong phần sau của tập lệnh, nó không rm tất cả các *.old.*tệp.

Có gì sai trong tập lệnh bash của tôi:

 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

Câu trả lời:


4

Nếu tôi hiểu những gì bạn đang làm (xóa bất kỳ tệp nào có .oldhậu tố và tạo một bản sao của bất kỳ tệp nào có .oldhậu tố), bạn có thể sử dụng find thay thế:

#!/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 0dừng lệnh find tìm trong thư mục con, chỉ -type ftìm các tệp thông thường; -printftạo tin nhắn ( %Plà tên tệp được tìm thấy). Các -exec cpgọi là chức năng sao chép và '{}'là tên file


14

Có nhiều điểm thất bại có thể có trong kịch bản của bạn. Trước hết, rm *.old*sẽ sử dụng globalbing để tạo danh sách tất cả các tệp phù hợp và có thể xử lý tên tệp chứa khoảng trắng. Kịch bản của bạn, tuy nhiên, gán một biến cho từng kết quả của toàn cầu và thực hiện điều đó mà không cần trích dẫn. Điều đó sẽ phá vỡ nếu tên tệp của bạn chứa khoảng trắng. Ví dụ:

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

Như bạn có thể thấy, vòng lặp thất bại cho tệp có khoảng trắng trong tên của nó. Để làm điều đó một cách chính xác, bạn sẽ cần trích dẫn biến:

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

Vấn đề tương tự áp dụng cho hầu hết mọi sử dụng $itrong tập lệnh của bạn. Bạn nên luôn luôn trích dẫn các biến của bạn .

Vấn đề có thể xảy ra tiếp theo là bạn dường như mong đợi *.old.*khớp các tệp với tiện ích mở rộng .old. Nó không. Nó khớp với "0 hoặc nhiều ký tự" ( *), sau đó là a ., rồi "cũ", rồi một ký tự khác .và sau đó "0 hoặc nhiều ký tự nữa". Điều này có nghĩa là nó sẽ không khớp với một cái gì đó như file.old, nhưng chỉ một cái gì đó như `file.old.foo:

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

Vì vậy, không có kẻ thù phù hợp file.old. Trong mọi trường hợp, kịch bản của bạn phức tạp hơn nhiều so với cần thiết. Hãy thử cái này thay thế:

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

Lưu ý rằng tôi đã thêm -vvào các câu lệnh rmvà cp which does the same thing as what you were doing with yourecho`.

Điều này không hoàn hảo vì khi bạn tìm thấy, ví dụ, file.oldnó sẽ bị xóa và sau đó, tập lệnh sẽ cố gắng sao chép nó và thất bại vì tệp không còn tồn tại. Tuy nhiên, bạn đã không giải thích những gì kịch bản của bạn thực sự đang cố gắng để tôi không thể khắc phục điều đó cho bạn trừ khi bạn cho chúng tôi biết những gì bạn đang thực sự cố gắng thực hiện.

Nếu điều bạn muốn là i) xóa tất cả các tệp có .oldtiện ích mở rộng và ii) thêm .oldtiện ích mở rộng vào bất kỳ tệp hiện có nào không có, tất cả những gì bạn thực sự cần là:

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

Tôi đang cố gắng sao lưu các tệp vào file.old nhưng đồng thời rm bất kỳ tệp nào kết thúc bằng .old.old hoặc .old.old.old hoặc .old.old.old, v.v. Trên dòng lệnh tôi sử dụng rm *.old.*để loại bỏ các tệp này nhưng không phải là tập tin sao lưu file.old. Tôi đang cố gắng làm điều đó trong kịch bản của tôi. Cảm ơn
Don

1
@Don vui lòng chỉnh sửa câu hỏi của bạn và giải thích điều này chi tiết hơn. Bao gồm tên tệp ví dụ và những gì bạn muốn xảy ra với chúng sau khi tập lệnh của bạn được chạy. Lý tưởng nhất là vào trò chuyện và ping tôi ở đó để chúng ta có thể thảo luận về nó.
terdon

8

Các trường hợp chỉ rm $oldfilecó thể thất bại là khi tên tập tin của bạn có chứa bất kỳ ký tự của IFS(không gian, tab, xuống dòng) hoặc bất kỳ ký tự glob ( *, ?, []).

Nếu có bất kỳ ký tự nào IFSxuất hiện, shell sẽ thực hiện phân tách từ và dựa trên sự hiện diện của việc mở rộng tên đường dẫn ký tự trên toàn cầu khi mở rộng biến.

Vì vậy, ví dụ, nếu tên tệp là foo bar.old., biến oldfilesẽ chứa foo bar.old..

Khi bạn làm:

rm $oldfile

shell lúc đầu phân chia sự mở rộng của oldfilekhông gian thành hai từ, foobar.old.. Vì vậy, lệnh trở thành:

rm foo bar.old.

mà rõ ràng sẽ dẫn đến kết quả bất ngờ. Bằng cách này, nếu bạn có bất kỳ nhà khai thác globbing ( *, ?, []) trong việc mở rộng, sau đó mở rộng tên đường dẫn sẽ được thực hiện quá.

Bạn cần trích dẫn các biến để có kết quả mong muốn:

rm "$oldfile"

Bây giờ, không có phân tách từ hoặc mở rộng tên đường dẫn sẽ được thực hiện, do đó bạn sẽ nhận được kết quả mong muốn, tức là tệp mong muốn sẽ bị xóa. Nếu bất kỳ tên tệp nào xảy ra để bắt đầu -, thì hãy làm:

rm -- "$oldfile"

Bạn có thể hỏi, tại sao chúng ta không cần trích dẫn các biến khi được sử dụng bên trong [[, lý do [[là một bashtừ khóa và nó xử lý việc mở rộng biến trong nội bộ giữ cho mở rộng theo nghĩa đen.


Bây giờ, vài điểm:

  • Bạn nên chuyển hướng STDERR ( exec 2>errorfile) trước rmlệnh nếu không [[ -s errorfile ]]kiểm tra sẽ cho kết quả dương tính giả

  • Bạn đã sử dụng [ -s $errorfile ], bạn đang sử dụng một mở rộng biến $errorfile, đó là errorfilebiến NUL đã cho không được xác định ở bất cứ đâu. Có lẽ bạn có nghĩa là, chỉ [ -s errorfile ], dựa trên chuyển hướng STDERR

  • Nếu biến errorfileđược xác định, trong khi sử dụng [ -s $errorfile ], nó sẽ lại bị nghẹt thở đối với các trường hợp IFSvà toàn cầu đã đề cập ở trên vì không giống như [[, [không được xử lý nội bộ bởibash

  • Trong phần sau của tập lệnh, bạn đang cố gắng cpxóa tệp đã bị xóa (một lần nữa mà không trích dẫn biến), điều này không có ý nghĩa gì, bạn nên kiểm tra chuck đó và thực hiện các chỉnh sửa cần thiết dựa trên mục tiêu của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.