rm funciona na linha de comando, mas não no script

9

Quando eu faço rm *.old.* na linha de comando, ele remove corretamente mas quando eu faço isso na parte seguinte do meu script, ele não rm todos os arquivos *.old.* .

O que está errado no meu 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
    
por Don 11.09.2016 / 14:58

3 respostas

3

Se eu entendi o que você está fazendo (exclua todos os arquivos com o sufixo .old e faça uma cópia de todos os arquivos existentes com um sufixo .old ), você pode usar localizar:

#!/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 pára o comando find procurando em subdiretórios, -type f procura somente por arquivos regulares; -printf cria mensagens ( %P é o nome do arquivo encontrado). O -exec cp chama a função de cópia e '{}' é o nome do arquivo

    
por Nick Sillito 11.09.2016 / 18:48
13

Existem vários pontos de falha possíveis no seu script. Antes de mais nada, rm *.old* usará globbing para criar uma lista de todos os arquivos correspondentes e que podem lidar com nomes de arquivos contendo espaço em branco. Seu script, no entanto, atribui uma variável a cada resultado do glob e faz isso sem citar. Isso irá quebrar se seus nomes de arquivo contiverem espaços em branco. Por exemplo:

$ 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 você pode ver, o loop falhou para o arquivo com espaços em seu nome. Para fazer isso corretamente, você precisaria citar a variável:

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

O mesmo problema se aplica a praticamente todo uso de $i em seu script. Você deve sempre citar suas variáveis .

O próximo problema possível é que você espera que *.old.* corresponda aos arquivos com a extensão .old . Não faz. Ele corresponde a "0 ou mais caracteres" ( * ), depois a . , depois "old", depois outro . e "0 ou mais caracteres novamente". Isso significa que não corresponde a algo como file.old , mas apenas 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     

Portanto, não há correspondência para file.old . Em qualquer caso, seu script é muito mais complexo do que o necessário. Tente este em vez disso:

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

Observe que adicionei -v às declarações rm e cp which does the same thing as what you were doing with your echo '.

Isso não é perfeito desde quando você encontra, por exemplo, file.old , que será removido e, mais tarde, o script tentará copiá-lo e falhar, pois o arquivo não existe mais. No entanto, você não explicou o que seu script está realmente tentando fazer, então não posso consertar isso para você, a menos que você nos diga o que realmente está tentando realizar.

Se o que você quer é i) remover todos os arquivos com a extensão .old e ii) adicionar a extensão .old a todos os arquivos existentes que não a possuem, tudo o que você realmente precisa é:

#!/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
    
por terdon 11.09.2016 / 15:54
7

Os únicos casos em que rm $oldfile pode falhar são quando o nome do arquivo contém qualquer caractere de IFS (espaço, tabulação, nova linha) ou qualquer caractere glob ( * , ? , [] ).

Se algum caractere de IFS estiver presente, o shell executará a divisão de palavras e com base na presença de expansão de nome de caminho de caracteres globbing na expansão de variável.

Assim, por exemplo, se o nome do arquivo for foo bar.old. , a variável oldfile conterá foo bar.old. .

Quando você faz:

rm $oldfile
O

shell primeiro divide a expansão de oldfile no espaço em duas palavras, foo e bar.old. . Então o comando se torna:

rm foo bar.old.

que obviamente levaria a resultados inesperados. A propósito, se você tiver operadores de globbing ( * , ? , [] ) na expansão, a expansão do nome de caminho também seria feita.

Você precisa citar as variáveis para obter o resultado desejado:

rm "$oldfile"

Agora, nenhuma expansão de palavra ou de nome de caminho seria feita, portanto, você deve obter o resultado desejado, ou seja, o arquivo desejado seria removido. Se algum nome de arquivo começar com - , faça:

rm -- "$oldfile"

Você pode perguntar por que não precisamos citar variáveis quando usado dentro de [[ , a razão sendo [[ é uma palavra-chave bash e lida com expansão de variáveis internamente mantendo o literal de expansão.

Agora, alguns pontos:

  • Você deve redirecionar STDERR ( exec 2>errorfile ) antes do comando rm , caso contrário, [[ -s errorfile ]] test fornecerá falsos positivos

  • Você usou [ -s $errorfile ] , você está usando uma variável de expansão $errorfile , que seria NUL, dado que errorfile variable não está definida em lugar algum. Talvez você quis dizer, apenas [ -s errorfile ] , com base no redirecionamento STDERR

  • Se a variável errorfile for definida, enquanto estiver usando [ -s $errorfile ] , ela novamente seria bloqueada nos casos mencionados acima de IFS e globbing porque, diferentemente de [[ , [ não é tratado internamente em bash

  • Na parte posterior do script, você está tentando cp do arquivo já removido (novamente sem citar a variável), isso não faz sentido, você deve verificar esse mandril e fazer as correções necessárias com base no seu alvo.

por heemayl 11.09.2016 / 15:55

Tags