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

11

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

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

Respostas:

4

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

#!/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 0interrompe o comando find procurando em subdiretórios, -type fprocura apenas arquivos regulares; -printfcria mensagens ( %Pé o nome do arquivo encontrado). A -exec cpchama a função de cópia e '{}'é o nome do arquivo

Nick Sillito
fonte
14

Existem vários pontos de falha possíveis no seu script. Primeiro, rm *.old*use globbing para criar uma lista de todos os arquivos correspondentes, e isso pode lidar com nomes de arquivos contendo espaços em branco. Seu script, no entanto, atribui uma variável a cada resultado da glob e faz isso sem citar. Isso será interrompido se os nomes dos arquivos 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 no arquivo com espaços em seu nome. Para fazer isso corretamente, você precisa 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 todos os usos do $iseu 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. Corresponde a "0 ou mais caracteres" ( *), depois a ., "antigo", depois outro .e depois a "0 ou mais caracteres novamente". Isso significa que não corresponderá 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     

Então, não há rival file.old. De qualquer forma, seu script é muito mais complexo do que o necessário. Experimente este:

#!/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 eu adicionei -vàs instruções rme cp which does the same thing as what you were doing with yourecho`.

Isso não é perfeito, pois quando você descobrir, por exemplo, file.oldque 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ê deseja é: i) remover todos os arquivos com a .oldextensão e ii) adicionar a .oldextensão a qualquer arquivo existente que não o possua, 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
Terdon
fonte
Estou tentando fazer backup de arquivos em file.old, mas ao mesmo tempo rm os arquivos que terminam em .old.old ou .old.old.old ou .old.old.old etc. Na linha de comando, uso o rm *.old.*que remove esses arquivos. mas não o arquivo de backup file.old. Estou tentando fazer isso no meu script. Obrigado
Don
1
@ Don, edite sua pergunta e explique isso com mais detalhes. Inclua nomes de arquivo de exemplo e o que você gostaria que acontecesse com eles após a execução do script. Idealmente, entre em bate - papo e faça ping comigo para que possamos discuti-lo.
terdon 11/09/16
8

Os únicos casos rm $oldfilepoderia falhar é quando o nome do arquivo contém qualquer caráter de IFS(espaço, tabulação, nova linha) ou qualquer personagem glob ( *, ?, []).

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

Portanto, por exemplo, se o nome do arquivo for foo bar.old., a variável oldfileconterá foo bar.old..

Quando você faz:

rm $oldfile

shell inicialmente divide a expansão do oldfileon space em duas palavras, fooe bar.old.. Então o comando se torna:

rm foo bar.old.

o que obviamente levaria a resultados inesperados. By the way, se você tiver quaisquer operadores englobamento ( *, ?, []) na expansão, em seguida, expansão de nome seria feito também.

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

rm "$oldfile"

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

rm -- "$oldfile"

Você pode perguntar por que não precisamos citar variáveis ​​quando usadas internamente [[, o motivo [[é uma bashpalavra - chave e ela lida com a expansão de variáveis ​​internamente, mantendo a expansão literal.


Agora, alguns pontos:

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

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

  • Se a variável errorfilefor definida, durante o uso [ -s $errorfile ], ela se engasgaria novamente com os casos acima mencionados IFSe globbing porque [[, diferentemente , [não é tratada internamente porbash

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

heemail
fonte