rm fonctionne sur la ligne de commande mais pas dans le script


11

Quand je le fais rm *.old.*sur la ligne de commande, il supprime correctement, mais quand je le fais dans la partie suivante de mon script, il ne rm tous les *.old.*fichiers.

Quel est le problème dans mon script bash:

 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

Réponses:


4

Si je comprends ce que vous faites (supprimez tous les fichiers avec le .oldsuffixe et faites une copie de tous les fichiers existants avec un .oldsuffixe), vous pouvez utiliser find à la place:

#!/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 0arrête la recherche de la recherche dans les sous-répertoires, -type frecherche uniquement les fichiers normaux; -printfcrée des messages ( %Pest le nom de fichier trouvé). L' -exec cpappelle la fonction de copie et '{}'est le nom de fichier


14

Il existe différents points d'échec possibles dans votre script. Tout d'abord, rm *.old*utilisera le globbing pour créer une liste de tous les fichiers correspondants, et qui peut traiter les noms de fichiers contenant des espaces. Votre script, cependant, attribue une variable à chaque résultat du glob et le fait sans citer. Cela se cassera si vos noms de fichiers contiennent des espaces. Par exemple:

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

Comme vous pouvez le voir, la boucle a échoué pour le fichier avec des espaces dans son nom. Pour le faire correctement, vous devez citer la variable:

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

Le même problème s'applique à presque toutes les utilisations de $ivotre script. Vous devez toujours citer vos variables .

Le prochain problème possible est que vous semblez vous attendre à ce que les *.old.*fichiers correspondent à l'extension .old. Ce n'est pas le cas. Il correspond à "0 ou plusieurs caractères" ( *), puis à a ., puis à "old", puis à un autre .puis à "0 ou plusieurs caractères à nouveau". Cela signifie qu'il ne correspondra pas à quelque chose comme file.old, mais seulement à quelque chose comme `file.old.foo:

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

Donc, pas d'ennemi de match file.old. Dans tous les cas, votre script est beaucoup plus complexe que nécessaire. Essayez plutôt celui-ci:

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

Notez que j'ai ajouté -vaux instructions rmet cp which does the same thing as what you were doing with yourecho`.

Ce n'est pas parfait car lorsque vous trouvez, par exemple, file.oldcela sera supprimé et, plus tard, le script essaiera de le copier et échouera car le fichier n'existe plus. Cependant, vous n'avez pas expliqué ce que votre script tente réellement de faire, donc je ne peux pas résoudre ce problème pour vous à moins que vous ne nous disiez ce que vous essayez vraiment d'accomplir.

Si vous voulez i) supprimer tous les fichiers avec l' .oldextension et ii) ajouter l' .oldextension à tous les fichiers existants qui ne l'ont pas, tout ce dont vous avez vraiment besoin est:

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

J'essaie de sauvegarder des fichiers dans file.old mais en même temps rm tous les fichiers qui se terminent par .old.old ou .old.old.old ou .old.old.old etc. Sur la ligne de commande que j'utilise rm *.old.*qui supprime ces fichiers mais pas le fichier de sauvegarde file.old. J'essaie de le faire dans mon script. Merci
Don

1
@Don veuillez modifier votre question et expliquer cela plus en détail. Incluez des exemples de noms de fichiers et ce que vous aimeriez leur arriver après l'exécution de votre script. Idéalement, venez discuter et envoyez-moi un ping pour que nous puissions en discuter.
terdon

8

Les seuls cas rm $oldfilepourraient échouer sont lorsque votre nom de fichier contient un caractère de IFS(espace, tabulation, saut de ligne) ou tout caractère glob ( *, ?, []).

Si un caractère de IFSest présent, le shell effectuera la division des mots et en fonction de la présence de l'extension des noms de chemin des caractères globbing sur l'expansion variable.

Ainsi, par exemple, si le nom du fichier est foo bar.old., la variable oldfilecontiendra foo bar.old..

Quand vous faites:

rm $oldfile

shell divise d'abord l'expansion de l' oldfileespace en deux mots, fooet bar.old.. La commande devient donc:

rm foo bar.old.

ce qui conduirait évidemment à un résultat inattendu. Soit dit en passant, si vous avez des opérateurs jokers ( *, ?, []) dans l'expansion, puis l' expansion chemin serait fait aussi.

Vous devez citer les variables pour obtenir le résultat souhaité:

rm "$oldfile"

Maintenant, aucun fractionnement de mot ou expansion de nom de chemin ne serait effectué, vous devriez donc obtenir le résultat souhaité, c'est-à-dire que le fichier souhaité serait supprimé. Si un nom de fichier commence par -, alors:

rm -- "$oldfile"

Vous pourriez vous demander pourquoi nous n'avons pas besoin de citer des variables lorsqu'elles sont utilisées à l'intérieur [[, la raison en [[est un bashmot-clé et il gère l'expansion des variables en interne en conservant le littéral de l'expansion.


Maintenant, quelques points:

  • Vous devez rediriger STDERR ( exec 2>errorfile) avant la rmcommande sinon le [[ -s errorfile ]]test donnerait des faux positifs

  • Vous avez utilisé [ -s $errorfile ], vous utilisez une extension de variable $errorfile, qui serait NUL étant donné que la errorfilevariable n'est définie nulle part. Peut-être que vous vouliez dire, simplement [ -s errorfile ], basé sur la redirection STDERR

  • Si la variable errorfileest définie, lors de l'utilisation [ -s $errorfile ], elle étoufferait à nouveau les cas mentionnés ci-dessus IFSet la globalisation car contrairement à [[, elle [n'est pas gérée en interne parbash

  • Dans la dernière partie du script, vous essayez cple fichier déjà supprimé (encore une fois sans citer la variable), cela n'a aucun sens, vous devez vérifier ce mandrin et apporter les corrections nécessaires en fonction de votre cible.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.