Jak mogę sprawdzić dosłowne znaki ciągu poleceń bash?

15

Miałem dziwne zachowanie dziś rano w terminalu 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
  • Pierwsze polecenie zostało wklejone ze skryptu edytowanego za pomocą gedit.
  • Drugi wpisano bezpośrednio w terminalu.

Po pewnym kopaniu dowiaduję się, że usunięcie 30-tego znaku (spacja między klientem.conf a „]”) i zastąpienie go spacją sprawiło, że polecenie znów działało.

Moje założenie było słuszne: nieznana pusta postać wpadła do komendy , ale pytanie brzmi:

  1. Jak mogę ujawnić te znaki w terminalu, aby móc debugować polecenie? I co ważniejsze:
  2. Jak mogę temu zapobiec?

BTW, mam system Ubuntu 18.04 / francuski, skrypt, z którego wklejam polecenie, znajduje się na dysku USB i mógł być edytowany także w systemie Windows.


Dziękuję za bardzo dobre odpowiedzi. Zły znak to znak UTF-8, który nie łamie c2 a0 . Pytanie, jak usunąć specjalną postać „M-BM-” za pomocą sed, ma interesujący fakt na temat tej postaci.

Dziwne jest to, że skrypt nie zawiera tej postaci. Więc nie wiem skąd się wziął.

Gabriel Glenn
źródło
3
Użyj edytora, który wyróżnia takie postacie. Podświetlanie składni również bardzo pomaga. Nigdy nie wklejaj bezpośrednio z sieci do terminala, zawsze korzystaj z wyżej wymienionego edytora.
choroba
2
Możesz chcieć znaleźć polecenie problemu na liście historii, a następnie przesłać dane wyjściowe przez program do wyświetlania w trybie szesnastkowym. Aby nie musieć przedzierać się przez długą listę, albo ponownie uruchom polecenie, aby umieścić je na dole listy historii i uruchom history 2|xxd(ponieważ historysamo polecenie jest zawsze ostatnie na liście), lub wpisz history|grep "CommandWithProblem"|xxd. Zamiast tego możesz użyć dowolnego innego programu do wyświetlania szesnastkowego xxd, ale domyślnie jest to format, który mi się podoba.
AFH
@Gabriel Glenn, proszę zaznaczyć najlepszą / najbardziej pomocną / jakąkolwiek odpowiedź jako „ zaakceptowaną ” za pomocą haczyka - zamiast komentować każdą z odpowiedzi, która pomogła. informacje
Attie
1
@Attie, tak, zwykle po prostu czekam 24 godziny, zanim zaakceptuję najlepsze odpowiedzi, zgodnie z sugestią: meta.stackexchange.com/questions/5234/...
Gabriel Glenn
1
Osobiście skorzystałbym set -x. To pokaże ci polecenie i jak jest podzielone. To niekoniecznie oznaczałoby „zły charakter tutaj”, ale pokazywałoby, że bash nie dzielił się z tą postacią.
Patrick,

Odpowiedzi:

11

Jedną z opcji jest spojrzenie na znaki, których próbujesz użyć w przeglądarce szesnastkowej lub edytorze. hexdumpjest dobrym rozwiązaniem, jeśli jesteś ograniczony do terminala.

$ 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

Widać tutaj, że space, close-square-brace, spacesą prawidłowe - 0x20, 0x5D, 0x20.

Te wartości są kodami ASCII wyświetlanymi w systemie szesnastkowym . Każda wartość spoza zakresu 0x20- 0x7Enie jest drukowalnym znakiem w odniesieniu do ASCII i najprawdopodobniej nie będzie dobrze grała z interfejsami linii poleceń.

Uwaga: Skopiowałem pierwszą „ przerwaną ” linię do użycia w hexdumppowyższym przykładzie, więc coś zastąpiło spację inną niż ASCII spacją ASCII między oryginalnym źródłem a renderowanym pytaniem.


Aby powtórzyć, wykonaj następujące czynności:

  1. Wpisz hexdump -Cv <<"EOF"i naciśnijEnter
  2. Wklej tekst, którego chcesz użyć
  3. Wpisz EOFwłasną linię i naciśnijEnter

Terminale i interfejsy wiersza poleceń nie radzą sobie dobrze ze znakami specjalnymi - jak odkryłeś. Jeśli nie jesteś bardzo ostrożny z formatowaniem dokumentów, będziesz mieć również problemy z Microsoft Word (i innymi) za pomocą „ inteligentnych cytatów ”, myślników, lista jest długa ...

Dostrzeż różnicę: (góra to „ inteligentne cytaty ”, dół to „ proste cytaty ”)

przykład inteligentnych cytatów vs proste cytaty

$ 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

Tutaj otwarte cytaty nie są prostą ASCII cytat ( "), lecz są Unicode / UTF-8 series - 0xE2, 0x80, 0x9Club U+201C- co terminal nie będzie obsługiwać jak można się było spodziewać.

Sugestia Kiwy'ego cat -Arównież działa:

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

Uwaga: podczas używaniaecho "..." | hdmasz szansę, że bash zastąpi części ciągu, które próbujesz sprawdzić. Jest to szczególnie niepokojące przy próbie sprawdzenia składników skryptu.

Na przykład spróbuj:

$ echo "${USER}"
attie

$ echo "`whoami`"
attie

$ echo "$(whoami)"
attie

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

Te metody zastępują komponenty odpowiednim tekstem. Aby tego uniknąć, zastosuj jedno z następujących podejść. Zwróć uwagę na użycie pojedynczego cudzysłowu ( ') i cytowanego heredoc ( "EOF").

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

$ echo '`whoami`'
`whoami`

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

$ cat <<"EOF"
> ${USER}
> EOF
${USER}
Attie
źródło
To rozwiązanie działa: echo "[ -f /etc/openvpn.ovpn ]" | hd zwraca [...] c2 a0 [...]. Widzimy nieprzerwaną przestrzeń c2 a0 UT-8
Gabriel Glenn
18

Możesz użyć catz -Aopcją: z instrukcji:

   -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

Więc cat -A yourscrip.shpokaże ci niewidzialne i dziwne postacie.

Kiwy
źródło
7
To rozwiązanie działa: echo "[ -f /etc/openvpn.ovpn ]" | cat -Azwraca [ -f /etc/openvpn/client.ovpnM-BM- ]$. Widzimy nieprzerwaną przestrzeń postaci M-BM- UT-8
Gabriel Glenn
@GabrielGlenn cieszę się, że to ci pomogło.
Kiwy
9

echo "<your command>" | hdpowinno działać. Poszukaj backspace (0x08) lub znaków z kodami> = 80. echo "<your command>" | wc -bdobrym pomysłem jest również sprawdzenie, czy liczba zgadza się z tym, co widzisz.

Kopiowanie plików z plików utworzonych przez cokolwiek z „Office” w nazwie jest niebezpieczne, ponieważ takie oprogramowanie często pozwala na zamianę znaków: po francusku wypatruj podwójnych cudzysłowów zastąpionych przez „gilemety”, po angielsku zwykłych cytatów zastępowanych przez ich otwieranie / zamykanie odpowiedników. Najtrudniejsze, jakie kiedykolwiek znalazłem, to niezniszczalna przestrzeń o szerokości 0 w środku nazwy pliku (3 dni przestoju serwera ...).

ksenoid
źródło
2
Warto wspomnieć, że hdskrót hexdumpten jest również wspomniany w odpowiedzi Attie.
Mikael Kjær
@ MikaelKjær - w systemie Ubuntu hdjest równoważne z hexdump -C.
AFH
1
@xenoid: Powiedziałem „edytowane w systemie Windows”, nie edytowane za pomocą Office Writer, nie jesteśmy szaleni;). Jeśli był edytowany, był w Notepad ++.
Gabriel Glenn
1
To rozwiązanie działa: echo "[ -f /etc/openvpn.ovpn ]" | hd zwraca [...] c2 a0 [...]. Widzimy nieprzerwaną przestrzeń postaci C2 a0 UT-8
Gabriel Glenn
2

Bash i inne powłoki, takie jak zsh, mogą otwierać bieżący wiersz poleceń w edytorze. Domyślny skrót dla bash jest C-x C-e( CtrlX CtrlE), a to otwiera się w pierwszy dostępny z $VISUAL, $EDITORi emacs. W praktyce jest to nieocenione przy debugowaniu i modyfikowaniu złożonych poleceń. W zależności od tego, jak na to patrzysz, zsh jest tutaj bardziej przyjazny niż bash: kiedy edytor kończy działanie, bash natychmiast uruchamia polecenie, podczas gdy zsh czeka na naciśnięcie Enter(co daje większą szansę na edycję polecenia).

Po otwarciu polecenia w edytorze możesz skonfigurować edytory, aby wyświetlały znaki inne niż ASCII w różny sposób.

Na przykład za pomocą Vima , używając tych ustawień:

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

wprowadź opis zdjęcia tutaj

Lub, dostosowując metody innych odpowiedzi:

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
źródło