Como posso verificar os caracteres textuais de uma sequência de comandos do bash?

15

Eu tive esse comportamento estranho nesta manhã em um terminal do bash:

user@home:/home/user$ [ -f /etc/openvpn/client.conf ] && echo true
bash: [: missing «]»
user@home:/home/user$ [ -f /etc/openvpn/client.conf ] && echo true
true
  • O primeiro comando foi colado a partir de um script editado com o gedit.
  • O segundo foi digitado diretamente no terminal.

Após algumas pesquisas, descobri que remover o 30º caractere (o espaço entre client.conf e "]") e substituí-lo por um espaço fazia com que o comando funcionasse novamente.

Minha suposição estava certa: um caractere em branco desconhecido entrou no comando , mas a pergunta é:

  1. Como posso revelar esses caracteres no terminal para poder depurar o comando? E mais importante:
  2. Como impedir que isso aconteça novamente?

BTW, eu executando o Ubuntu 18.04 / idioma francês, o script do qual colo o comando está em uma unidade USB e também pode ter sido editado no Windows.


Obrigado por suas respostas muito boas. O caractere inválido é um caractere UTF-8 de espaço c2 a0 . A questão Como remover o caractere especial 'M-BM-' com o sed tem um fato interessante sobre esse caractere.

O estranho é que o script está livre desse personagem. Então eu não sei de onde veio.

Gabriel Glenn
fonte
3
Use um editor que destaque esses caracteres. O destaque da sintaxe também ajuda muito. Nunca cole diretamente da web no terminal, sempre passe pelo editor acima mencionado.
choroba 16/05
2
Você pode encontrar o comando do problema na sua lista de histórico e canalizar a saída através de um programa de exibição hexadecimal. Para que você não precise percorrer uma lista longa, execute novamente o comando para colocá-lo no final da lista do histórico e execute history 2|xxd(porque o historypróprio comando é sempre o último da lista) ou digite history|grep "CommandWithProblem"|xxd. Você pode usar qualquer outro programa de exibição hexadecimal em vez de xxd, mas o padrão é o formato que eu gosto.
AFH
@ Gabriel Glenn, marque a resposta melhor / mais útil / qualquer que seja como " aceita " usando o visto - em vez de comentar em cada uma delas que a resposta ajudou. informação
Attie
11
@Attie, Sim, eu vou, eu só costumam esperar 24 horas antes de aceitar as melhores respostas, como sugerido em: meta.stackexchange.com/questions/5234/...
Gabriel Glenn
11
Pessoalmente eu usaria set -x. Isso mostraria o comando e como ele é dividido. Não seria necessariamente dizer "personagem ruim aqui", mas mostraria que o bash não estava se dividindo nesse personagem.
187 Patrick #

Respostas:

11

Uma opção é observar os caracteres que você está tentando usar com um visualizador ou editor hexadecimal. hexdumpé uma boa opção se você estiver limitado ao terminal.

$ hexdump -Cv <<"EOF"
> [ -f /etc/openvpn/client.conf ] && echo true
> EOF
00000000  5b 20 2d 66 20 2f 65 74  63 2f 6f 70 65 6e 76 70  |[ -f /etc/openvp|
00000010  6e 2f 63 6c 69 65 6e 74  2e 63 6f 6e 66 20 5d 20  |n/client.conf ] |
00000020  26 26 20 65 63 68 6f 20  74 72 75 65 0a           |&& echo true.|
0000002d

Você pode ver aqui que o space, close-square-brace, spaceestão corretas - 0x20, 0x5D, 0x20.

Esses valores são códigos ASCII, exibidos em hexadecimal . Qualquer valor fora do intervalo 0x20- 0x7Enão é um " caractere imprimível " no que diz respeito ao ASCII e provavelmente não funcionará bem com interfaces de linha de comando.

Nota: Copiei sua primeira linha " quebrada " para uso no hexdumpexemplo acima, portanto, algo substituiu o espaço não-ASCII por um espaço ASCII entre sua fonte original e sua pergunta renderizada.


Para repetir isso, execute as seguintes etapas:

  1. Digite hexdump -Cv <<"EOF"e pressioneEnter
  2. Cole o texto que você gostaria de usar
  3. Digite EOFem uma linha própria e pressioneEnter

Terminais e interfaces de linha de comando não lidam bem com caracteres especiais - como você descobriu. Se você não for muito cuidadoso com a formatação de documentos, também terá problemas com o Microsoft Word (e outros) usando " aspas inteligentes ", traços, a lista continua ...

Descubra a diferença: (a parte superior é " aspas inteligentes ", a parte inferior é " aspas diretas ")

exemplo de aspas inteligentes vs aspas simples

$ hexdump -Cv <<"EOF"
> quoted string
> EOF
00000000  e2 80 9c 71 75 6f 74 65  64 20 73 74 72 69 6e 67  |...quoted string|
00000010  e2 80 9d 0a                                       |....|
00000014

Aqui, as aspas abertas não são uma citação ASCII simples ( "), mas estão Unicode / UTF-8 séries - 0xE2, 0x80, 0x9Cou U+201C- que o terminal não irá lidar com como você poderia esperar.

A sugestão de Kiwy cat -Atambém faz o trabalho:

$ cat -A <<"EOF"
> quoted string
> EOF
M-bM-^@M-^\quoted stringM-bM-^@M-^]$

Nota: ao usarecho "..." | hd, você tem a chance de que o bash substitua partes da string que você está tentando inspecionar. Isso é particularmente preocupante ao tentar inspecionar componentes de um script.

Por exemplo, tente:

$ echo "${USER}"
attie

$ echo "`whoami`"
attie

$ echo "$(whoami)"
attie

$ cat <<EOF
> ${USER}
> EOF
attie

Esses métodos estão substituindo componentes pelo texto relevante. Para evitar isso, use uma das seguintes abordagens. Observe o uso de aspas simples ( ') e um " heredoc citado " ( "EOF").

$ echo '${USER}'
${USER}

$ echo '`whoami`'
`whoami`

$ echo '$(whoami)'
$(whoami)

$ cat <<"EOF"
> ${USER}
> EOF
${USER}
Attie
fonte
Esta solução funciona: echo "[ -f /etc/openvpn.ovpn ]" | hd retorna [...] c2 a0 [...]. Podemos ver o c2 a0 UT-8 caracteres espaço não-quebra
Gabriel Glenn
18

Você pode usar catcom a -Aopção: no manual:

   -A, --show-all
          equivalent to -vET
   -E, --show-ends
          display $ at end of each line
   -T, --show-tabs
          display TAB characters as ^I
   -v, --show-nonprinting
          use ^ and M- notation, except for LFD and TAB

Então cat -A yourscrip.sh, você mostrará personagens invisíveis e estranhos.

Kiwy
fonte
7
Esta solução funciona: echo "[ -f /etc/openvpn.ovpn ]" | cat -Aretorna [ -f /etc/openvpn/client.ovpnM-BM- ]$. Podemos ver a M-BM UT-8 caracteres espaço não-quebra
Gabriel Glenn
@GabrielGlenn que bom que isso ajudou você.
Kiwy
9

echo "<your command>" | hdDeveria trabalhar. Procure backspace (0x08) ou caracteres com códigos> = 80. echo "<your command>" | wc -be verificar se a contagem corresponde ao que você vê também é uma boa ideia.

Copiar coisas de arquivos produzidos por qualquer coisa com "Office" em seu nome é perigoso, porque esse software geralmente tem a liberdade de substituir caracteres: em francês, procure aspas duplas substituídas por "guillemets", em inglês para aspas simples substituídas por seus abrir / fechar equivalentes. O mais difícil que eu já encontrei foi um espaço sem quebra de largura 0 no meio de um nome de arquivo (3 dias de inatividade do servidor ...).

xenoid
fonte
2
Vale a pena mencionar que hdé curto hexdumpe também é mencionado na resposta de Attie.
Mikael Kjær
@ MikaelKjær - No Ubuntu, hdé equivalente a hexdump -C.
AFH
11
@xenoid: Eu disse 'editado no Windows', não editado com o Office Writer, não somos loucos;). Se foi editado, foi com o Notepad ++.
Gabriel Glenn
11
Esta solução funciona: echo "[ -f /etc/openvpn.ovpn ]" | hd retorna [...] c2 a0 [...]. Podemos ver o c2 a0 UT-8 caracteres espaço não-quebra
Gabriel Glenn
2

O Bash e outros shells, como o zsh, podem abrir a linha de comando atual em um editor. O atalho padrão para a festança é C-x C-e( CtrlX CtrlE), e abre no primeiro disponível de $VISUAL, $EDITORe emacs. Na prática, isso é inestimável para depuração e modificação de comandos complexos. Dependendo de como você olha, o zsh é mais amigável do que o bash aqui: quando o editor sai, o bash executa o comando imediatamente, enquanto o zsh espera que você pressione Enter(dando mais chances de editar o comando).

Depois de abrir o comando em um editor, você pode configurar seus editores para mostrar caracteres não ASCII de maneira diferente.

Por exemplo, com o Vim , use estas configurações:

set encoding=latin1
set isprint=
set display+=uhex

insira a descrição da imagem aqui

Ou, adaptando os métodos das outras respostas:

bash-4.4$ f() { cat -A "$@"; false; }   # exit false to prevent bash from running the command
bash-4.4$ VISUAL=f
bash-4.4$ [ -f /etc/openvpn/client.conf ] && echo true  # C-x C-e here
[ -f /etc/openvpn/client.confM-BM- ] && echo true$
muru
fonte