Eu queria usar tr
para substituir caracteres "ilegais" em uma string por um caractere de substituição, onde os caracteres "ilegais" estão todos fora de um conjunto de caracteres "permitidos" (ou seja, são o complemento do conjunto de caracteres permitidos). No entanto, ao usar a -c
opção e o *
especificador de repetição explícito ou a extensão implícita do "conjunto 2", tr
anexa uma instância adicional do caractere de substituição à saída.
Reproduzir
- Sejam os caracteres "permitidos"
a-n
, especificados literalmente comoabcdefghijklmn
. - Deixe o caractere de substituição ser
z
. - Deixe a string de entrada ser
hell
ouhello
. A string de saída esperada é entãohell
ehellz
, respectivamente.
Demonstração
Caracteres ilegais presentes, extensão implícita do conjunto 2
$ echo "hello" | tr -c 'abcdefghijklmn' 'z' hellzz
A saída esperada é
hellz
.Somente caracteres permitidos presentes, extensão implícita do conjunto 2
$ echo "hell" | tr -c 'abcdefghijklmn' 'z' hellz
A saída esperada é
hell
.Caracteres ilegais presentes, extensão explícita do conjunto 2
$ echo "hello" | tr -c 'abcdefghijklmn' '[z*]' hellzz
A saída esperada é
hellz
.Somente caracteres permitidos presentes, extensão explícita do conjunto 2
$ echo "hell" | tr -c 'abcdefghijklmn' '[z*]' hellz
A saída esperada é
hell
.O mesmo acontece quando eu uso uma string here em vez de echo-pipe (na verdade, a string here foi a construção que usei quando me deparei com esse efeito pela primeira vez):
$ tr -c 'abcdefghijkl' '[z*]' <<< "hello" hellzz
Por que tr
acrescenta um adicional z
aqui?
Isso está no Linux, com bash, localidade UTF-8 e tr
no GNU coreutils 8.25 e 8.30.
Isso ocorre porque
echo
adiciona uma nova linha ao final do que você diz para imprimir. O mesmo acontece se você usar uma string here.Então,
echo "hello"
na verdade, imprimehello\n
:E é por isso que você vê isso:
Observe como não há nenhuma nova linha final e o
$
do meu prompt aparece logo após o últimoz
. Isso porque o\n
impresso no final dehello\n
é substituído por umz
. Se você usarprintf
, funcionará conforme o esperado:(
printf %s "$string"
para strings arbitrárias, não)printf "$string"
Ou, se você usar um
echo
que suporte isso, useecho -n
:Ou se você tiver um UNIX padrão
echo
(como oecho
integrado debash
quando as opçõesposix
expg_echo
estão habilitadas), use\c
which faz comecho
que a saída seja interrompida:Mas provavelmente você deseja preservar esse delimitador de linha na entrada para que a saída ainda seja um texto adequado:
(aqui usando a sintaxe POSIX padrão e
printf
em vez dissoecho
torna mais óbvio que uma nova linha é adicionada e também evita problemas com strings que começam-
ou contêm\
caracteres).Observe também que, dependendo da
tr
implementação, pode deixar os bytes que não podem ser decodificados apenas em caracteres (não alterados paraz
), enquanto em alguns outros como GNUtr
, isso só funciona corretamente para texto (e em localidades) com um único byte por caractere.Outra abordagem é usar
sed
qual, pelo menos com a implementação GNU, funciona melhor nesse aspecto:sed
funciona no conteúdo da linha, portanto os caracteres de nova linha são preservados automaticamente.