Substituindo caracteres no arquivo

0

Olá eu estou escrevendo script que leva 2 símbolos e substitui o primeiro com o segundo e também o script leva como um arquivo de argumentos. A idéia é substituir no arquivo o primeiro caractere com o segundo aqui é o script:

char_src=$1
char_dest=$2
shift

for file
do
    while read -r line
    do
        line=${line//$char_src/$char_dest}
        echo "$line"
    done < "$file"
done

O problema é que ele imprime o conteúdo corretamente, mas não salva as alterações no arquivo, eu tento salvá-las com done < "$file"

    
por Ivan Ivanov 04.06.2017 / 15:37

2 respostas

1

Primeiro a parte fácil: ler arquivos linha por linha a partir do shell é lento, você provavelmente vai querer usar tr . Veja man tr para detalhes.

Em segundo lugar, você precisa de um arquivo temporário para isso. Você não pode ler e escrever um arquivo ao mesmo tempo (não a partir do shell). Então você precisa fazer algo assim:

tr -- "$1" "$2" <"$file" >"$file".tmp
mv -f -- "$file".tmp "$file"

Um problema óbvio com isso é o que acontece se tr falhar, por qualquer razão (digamos, porque $1 está vazio). Então $file.tmp ainda será criado (é criado quando a linha é analisada pelo shell), então $file é substituído por ele.

Então, uma maneira um pouco mais segura de fazer isso ficaria assim:

tr -- "$1" "$2" <"$file" >"$file".tmp && \
mv -f -- "$file".tmp "$file"

Agora, $file é substituído apenas quando tr for bem-sucedido.

Mas e se houver outro arquivo chamado $file.tmp ao redor? Bem, isso será substituído. É aí que fica mais complicado: a maneira tecnicamente correta de fazer isso é algo assim:

tmp="$( mktemp -t "${0##*/}"_"$$"_.XXXXXXXX )" && \
trap 'rm -f "$tmp"' EXIT HUP INT QUIT TERM || exit 1
tr -- "$1" "$2" <"$file" >"$tmp" && \
cat -- "$tmp" >"$file" && \
rm -f -- "$tmp"

Aqui mktemp cria um arquivo temporário; trap garante que este arquivo seja limpo se o script sair (normalmente ou de forma anormal); e cat -- "$tmp" >"$file" é usado em vez de mv para garantir que as permissões de $file sejam preservadas. Isso ainda pode falhar se, por exemplo, você não tiver permissões de gravação em $file , mas o arquivo temporário será removido eventualmente.

Alternativamente, o GNU sed tem uma opção para editar arquivos no lugar, então você pode fazer algo assim:

sed -i "s/$1/$2/g" "$file"

No entanto, isso não é tão simples quanto parece. Os itens acima falharão se $1 ou $2 tiverem um significado especial para sed . Então você precisa escapar primeiro:

in=$(  printf '%s\n' "$1" | sed 's:[][\/.^$*]:\&:g' )
out=$( printf '%s\n' "$2" | sed 's:[\/&]:\&:g;$!s/$/\/' )
sed -i "s/$in/$out/" "$file"
    
por 04.06.2017 / 16:02
0

Ignorando por enquanto o fato de que existem ferramentas criadas para esse propósito.

char_src=$1
char_dest=$2
shift

Você provavelmente deseja shift 2 aqui, um shift comum moverá $2 para $1 e deixará você com o que estiver lá como o primeiro nome de arquivo.

    while read -r line

Você está usando corretamente read -r , mas observe que, com o padrão IFS , read removerá os espaços em branco inicial e final.

    done < "$file"

< apenas redireciona a entrada , para redirecionar a saída também, você precisaria de > "$file2" e, conforme observado em vários lugares (eg aqui ), redirecionando para o mesmo arquivo apenas truncará antes de ler qualquer coisa.

Sobre as ferramentas criadas para isso, para alterar caracteres únicos para outros caracteres únicos, use tr . A construção ${var//pat/repl} do shell substitui sequências inteiras e é mais parecida com s/pat/repl/g em sed .

    
por 04.06.2017 / 16:19