web-dev-qa-db-fra.com

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

Lorsque je fais rm *.old.* sur la ligne de commande, il est supprimé correctement, mais lorsque je le fais dans la partie suivante de mon script, il ne récupère pas tous les fichiers *.old.*.

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

Si je comprends ce que vous faites (supprimez tous les fichiers avec le suffixe .old et copiez tous les fichiers existants avec un suffixe .old), 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 0 arrête la commande de recherche dans les sous-répertoires, -type f recherche uniquement les fichiers normaux; -printf crée des messages (%P est le nom du fichier trouvé). Le -exec cp appelle la fonction de copie et '{}' est le nom du fichier.

3
Nick Sillito

Il existe différents points d'échec possibles dans votre script. Tout d'abord, rm *.old* utilisera globbing pour créer une liste de tous les fichiers correspondants et pouvant traiter des noms de fichiers contenant des espaces. Cependant, votre script assigne une variable à chaque résultat du glob et le fait sans les citer. Cela se brisera 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 constater, 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 concerne à peu près toutes les utilisations de $i dans votre script. Vous devriez toujours citer vos variables .

Le problème suivant est que vous semblez vous attendre à ce que *.old.* corresponde aux fichiers portant l'extension .old. Ce n'est pas. Il correspond à "0 caractères ou plus" (*), puis à un ., puis à "ancien", puis à un autre . et ensuite "0 ou plusieurs caractères à nouveau". Cela signifie que 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, aucun match ne correspond à file.old. Dans tous les cas, votre script est beaucoup plus complexe que nécessaire. Essayez celui-ci à la place:

#!/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é -v aux instructions rm et cpwhich does the same thing as what you were doing with yourecho`.

Ce n'est pas parfait, car lorsque vous trouvez, par exemple, file.old, celui-ci sera supprimé et que, plus tard, le script tentera 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’extension .old et ii) ajouter l’extension .old aux fichiers existants qui ne l’ont pas, il vous suffit de:

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

Les seuls cas où rm $oldfile pourrait échouer sont ceux où votre nom de fichier contient un caractère de IFS(espace, tabulation, nouvelle ligne) ou n’importe quel caractère global (*, ?, []).

Si un caractère de IFSest présent, le shell procédera au fractionnement de Word en fonction de la présence de caractères globaux, élargissant le chemin du nom du chemin de la variable.

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

Quand tu fais:

rm $oldfile

Dans un premier temps, Shell divise le développement de oldfilede l'espace en deux mots, fooet bar.old.. Donc la commande devient:

rm foo bar.old.

ce qui conduirait évidemment à un résultat inattendu. Soit dit en passant, si vous avez des opérateurs globants (*, ?, []) dans l’extension, l’extension du chemin sera également effectuée.

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

rm "$oldfile"

Désormais, aucun fractionnement de mot ou extension de nom de chemin ne serait effectué. Par conséquent, vous devriez 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 vous demandez peut-être pourquoi nous n’avons pas besoin de citer les variables lorsqu’elles sont utilisées dans [[, la raison étant [[ est un mot clé bashet il gère le développement des variables en interne en conservant le littéral de développement.


Maintenant, quelques points:

  • Vous devez rediriger STDERR (exec 2>errorfile) avant la commande rmsinon le test [[ -s errorfile ]] donnerait de faux positifs.

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

  • Si la variable errorfileest définie, si vous utilisez [ -s $errorfile ], les cas susmentionnés de IFSet de globbing mentionnés ci-dessus ne sont pas concernés car, contrairement à [[, [ n'est pas géré en interne par bashname__.

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

8
heemayl