rm funciona en la línea de comando pero no en el script

11

Cuando lo hago rm *.old.*en la línea de comando, se elimina correctamente, pero cuando lo hago en la siguiente parte de mi secuencia de comandos, no contiene todos los *.old.*archivos.

Lo que está mal en mi 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
Don
fuente

Respuestas:

4

Si entiendo lo que está haciendo (elimine los archivos con el .oldsufijo y haga una copia de los archivos existentes con un .oldsufijo), podría usar find en su lugar:

#!/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 0detiene el comando de búsqueda en subdirectorios, -type fbusca solo archivos normales; -printfcrea mensajes ( %Pes el nombre de archivo encontrado). El -exec cpllama a la función de copia y '{}'es el nombre del archivo

Nick Sillito
fuente
14

Hay varios puntos de falla posibles en su secuencia de comandos. En primer lugar, rm *.old*usará globbing para crear una lista de todos los archivos coincidentes, y eso puede tratar con nombres de archivos que contienen espacios en blanco. Sin embargo, su script asigna una variable a cada resultado del glob y lo hace sin citar. Eso se romperá si los nombres de sus archivos contienen espacios en blanco. Por ejemplo:

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

Como puede ver, el bucle falló para el archivo con espacios en su nombre. Para hacerlo correctamente, deberá citar la variable:

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

El mismo problema se aplica a casi todos los usos de $isu secuencia de comandos. Usted debe siempre citar sus variables .

El siguiente problema posible es que parece esperar que *.old.*coincida con los archivos con la extensión .old. No lo hace. Coincide con "0 o más caracteres" ( *), luego a ., luego "viejo", luego otro .y luego "0 o más caracteres nuevamente". Esto significa que no coincidirá con algo como file.old, sino solo algo como `file.old.foo:

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

Entonces, no hay rival para el enemigo file.old. En cualquier caso, su script es mucho más complejo de lo necesario. Prueba este en su lugar:

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

Tenga en cuenta que agregué -va las declaraciones rmy cp which does the same thing as what you were doing with yourecho`.

Esto no es perfecto ya que cuando encuentre, por ejemplo, file.oldque se eliminará y, más adelante, el script intentará copiarlo y fallará ya que el archivo ya no existe. Sin embargo, no ha explicado lo que su script realmente está intentando hacer, así que no puedo arreglarlo por usted a menos que nos diga lo que realmente está tratando de lograr.

Si lo que desea es i) eliminar todos los archivos con la .oldextensión y ii) agregar la .oldextensión a cualquier archivo existente que no lo tenga, todo lo que realmente necesita es:

#!/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
fuente
Estoy tratando de hacer una copia de seguridad de los archivos en file.old pero al mismo tiempo rm cualquier archivo que termine en .old.old o .old.old.old o .old.old.old, etc. En la línea de comando que uso, rm *.old.*que elimina estos archivos pero no el archivo de respaldo file.old. Estoy tratando de hacer eso en mi guión. Gracias
Don
1
@ No edite su pregunta y explique esto con más detalle. Incluya nombres de archivos de ejemplo y lo que le gustaría que les suceda después de ejecutar su script. Lo ideal es entrar al chat y enviarme un ping allí para que podamos discutirlo.
terdon
8

Los únicos casos rm $oldfilepodrían fallar son cuando su nombre de archivo contiene ningún carácter de IFS(espacio, tabulación, nueva línea) o cualquier carácter glob ( *, ?, []).

Si hay algún carácter de IFSpresente, el shell realizará la división de palabras y se basará en la presencia de expansión de nombres de ruta de caracteres globales en la expansión variable.

Entonces, por ejemplo, si el nombre del archivo es foo bar.old., la variable oldfilecontendría foo bar.old..

Cuando tu lo hagas:

rm $oldfile

Shell al principio divide la expansión del oldfileespacio en dos palabras, fooy bar.old.. Entonces el comando se convierte en:

rm foo bar.old.

lo que obviamente conduciría a un resultado inesperado. Por cierto, si tiene alguna operadores englobamiento ( *, ?, []) en la expansión, a continuación, la expansión de ruta sería hecho también.

Debe citar las variables para obtener el resultado deseado:

rm "$oldfile"

Ahora, no se realizaría la división de palabras ni la expansión del nombre de ruta, por lo tanto, debería obtener el resultado deseado, es decir, se eliminaría el archivo deseado. Si comienza algún nombre de archivo -, entonces:

rm -- "$oldfile"

Puede preguntar, por qué no necesitamos citar variables cuando se usa dentro [[, la razón [[es que es una bashpalabra clave y maneja la expansión de variables internamente manteniendo la expansión literal.


Ahora, un par de puntos:

  • Debe redirigir STDERR ( exec 2>errorfile) antes del rmcomando; de lo contrario, la [[ -s errorfile ]]prueba daría falsos positivos

  • Ha utilizado [ -s $errorfile ], está utilizando una expansión variable $errorfile, que sería NUL dado que la errorfilevariable no está definida en ninguna parte. Quizás quisiste decir, simplemente [ -s errorfile ], basado en la redirección STDERR

  • Si errorfilese define la variable , durante el uso [ -s $errorfile ], volvería a ahogarse en los casos mencionados anteriormente IFSy se bloqueará porque [[, a diferencia , [no se maneja internamente porbash

  • En la parte posterior de la secuencia de comandos, está intentando cpel archivo ya eliminado (de nuevo sin citar la variable), esto no tiene ningún sentido, debe verificar ese mandril y hacer las correcciones necesarias en función de su objetivo.

heemayl
fuente