rm arbeitet in der Kommandozeile, aber nicht im Skript

11

Wenn ich es rm *.old.*in der Befehlszeile mache , wird es korrekt entfernt, aber wenn ich es im folgenden Teil meines Skripts mache, werden nicht alle *.old.*Dateien rm .

Was ist falsch in meinem Bash-Skript:

 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
Don
quelle

Antworten:

4

Wenn ich verstehe, was Sie tun (löschen Sie alle Dateien mit dem .oldSuffix und erstellen .oldSie eine Kopie aller vorhandenen Dateien mit dem Suffix), können Sie stattdessen find verwenden:

#!/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 0Stoppt den Suchbefehl in Unterverzeichnissen und -type fsucht nur nach regulären Dateien. -printferstellt Nachrichten ( %Pist der gefundene Dateiname). Das -exec cpruft die Kopierfunktion auf und '{}'ist der Dateiname

Nick Sillito
quelle
14

Es gibt verschiedene mögliche Fehlerpunkte in Ihrem Skript. Zunächst rm *.old*wird Globbing verwendet , um eine Liste aller übereinstimmenden Dateien zu erstellen, die sich mit Dateinamen befassen können, die Leerzeichen enthalten. Ihr Skript weist jedoch jedem Ergebnis des Globs eine Variable zu und tut dies ohne Anführungszeichen. Das wird kaputt gehen, wenn Ihre Dateinamen Leerzeichen enthalten. Beispielsweise:

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

Wie Sie sehen können, ist die Schleife für die Datei mit Leerzeichen im Namen fehlgeschlagen. Um es richtig zu machen, müssten Sie die Variable zitieren:

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

Das gleiche Problem gilt für so ziemlich jede Verwendung $iin Ihrem Skript. Sie sollten immer Ihre Variablen zitieren .

Das nächste mögliche Problem ist, dass Sie anscheinend erwarten, dass *.old.*Dateien mit der Erweiterung übereinstimmen .old. Das tut es nicht. Es entspricht "0 oder mehr Zeichen" ( *), dann a ., dann "alt", dann einem anderen .und dann "0 oder mehr Zeichen wieder". Dies bedeutet, dass es nicht mit so file.oldetwas wie "file.old.foo" übereinstimmt:

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

Also kein Matchfeind file.old. In jedem Fall ist Ihr Skript weitaus komplexer als erforderlich. Versuchen Sie stattdessen dieses:

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

Beachten Sie, dass ich -vdie Anweisungen rmund cp which does the same thing as what you were doing with yourecho` hinzugefügt habe.

Dies ist nicht perfekt, da beispielsweise file.olddas Skript entfernt wird und später versucht, es zu kopieren, was fehlschlägt, da die Datei nicht mehr vorhanden ist. Sie haben jedoch nicht erklärt, was Ihr Skript tatsächlich versucht, daher kann ich das nicht für Sie beheben, es sei denn, Sie sagen uns, was Sie wirklich erreichen möchten.

Wenn Sie i) alle Dateien mit der .oldErweiterung entfernen und ii) die .oldErweiterung zu vorhandenen Dateien hinzufügen möchten , die sie nicht haben, brauchen Sie nur:

#!/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
Terdon
quelle
Ich versuche, Dateien in file.old zu sichern, aber gleichzeitig rm alle Dateien, die auf .old.old oder .old.old.old oder .old.old.old usw. enden. In der Befehlszeile, die ich verwende rm *.old.*, werden diese Dateien entfernt aber nicht die file.old Backup-Datei. Ich versuche das in meinem Skript zu tun. Danke
Don
1
@Don bitte bearbeiten Sie Ihre Frage und erklären Sie dies genauer. Geben Sie Beispieldateinamen an und was mit ihnen geschehen soll, nachdem Ihr Skript ausgeführt wurde. Kommen Sie im Idealfall in den Chat und rufen Sie mich dort an, damit wir darüber diskutieren können.
Terdon
8

Die einzigen Fälle , rm $oldfilescheitern könnte, wenn der Dateiname enthält alle Zeichen von IFS(Leerzeichen, Tabulator, neue Zeile) oder in einem Glob - Zeichen ( *, ?, []).

Wenn ein Zeichen von IFSvorhanden ist, führt die Shell eine Wortaufteilung durch und basiert auf dem Vorhandensein von Pfadnamenerweiterungen für globale Variablen in der Variablenerweiterung.

Wenn der Dateiname beispielsweise lautet foo bar.old., oldfilewürde die Variable enthalten foo bar.old..

Wenn Sie das tun:

rm $oldfile

Shell teilt zunächst die Erweiterung des oldfileRaums in zwei Wörter auf, foound bar.old.. So wird der Befehl:

rm foo bar.old.

was offensichtlich zu einem unerwarteten Ergebnis führen würde. By the way, wenn Sie irgendwelche Globbing Betreiber haben ( *, ?, []) in der Expansion, dann würde Pfadname Expansion zu erfolgen.

Sie müssen die Variablen angeben, um das gewünschte Ergebnis zu erhalten:

rm "$oldfile"

Jetzt würde keine Wortteilung oder Pfadnamenerweiterung mehr durchgeführt, daher sollten Sie das gewünschte Ergebnis erhalten, dh die gewünschte Datei würde entfernt. Wenn ein Dateiname mit beginnt -, gehen Sie wie folgt vor :

rm -- "$oldfile"

Sie könnten sich fragen, warum wir bei Verwendung im Inneren keine Variablen zitieren [[müssen. Der Grund dafür [[ist ein bashSchlüsselwort, das die Variablenerweiterung intern behandelt und das Erweiterungsliteral beibehält.


Nun ein paar Punkte:

  • Sie sollten STDERR ( exec 2>errorfile) vor dem rmBefehl umleiten, da sonst der [[ -s errorfile ]]Test falsch positive Ergebnisse liefern würde

  • Sie haben verwendet [ -s $errorfile ], Sie verwenden eine Variablenerweiterung $errorfile, die NUL wäre, wenn die errorfileVariable nirgendwo definiert ist. Vielleicht meinten Sie nur [ -s errorfile ], basierend auf der STDERR-Umleitung

  • Wenn die Variable errorfiledefiniert ist, während der Verwendung [ -s $errorfile ], würde es wieder ersticken oben genannten Fälle IFSund Globbing , weil im Gegensatz [[, [ist intern nicht behandelt durchbash

  • Im späteren Teil des Skripts versuchen Sie, cpdie bereits entfernte Datei zu verwenden (erneut ohne Angabe der Variablen). Dies macht keinen Sinn. Sie sollten dieses Spannfutter überprüfen und die erforderlichen Korrekturen basierend auf Ihrem Ziel vornehmen.

heemayl
quelle