bash nie może przechowywać wartości szesnastkowej 0x00 w zmiennej

11

Próbuję zrobić kilka sztuczek z dd. Myślałem, że możliwe będzie przechowywanie niektórych wartości szesnastkowych w zmiennej o nazwie „nagłówek”, aby przesłać je do dd.

Mój pierwszy krok bez zmiennej był następujący:

$ echo -ne "\x36\xc9\xda\x00\xb4" |dd of=hex
$ hd hex

00000000  36 c9 da 00 b4                                    |6....|
00000005

Potem próbowałem tego:

$ header=$(echo -ne "\x36\xc9\xda\x00\xb4") 
$ echo -n $header | hd

00000000  36 c9 da b4                                       |6...|
00000004

Jak widać straciłem swoją \x00wartość w $headerzmiennej. Czy ktoś ma wyjaśnienie tego zachowania? Doprowadza mnie to do szału.

Szczery
źródło
I dostać bash: warning: command substitution: ignored null byte in input.
Kusalananda
Brakuje cytatów, ale powinno to dać header="$(echo -ne "\x36\xc9\xda\x00\xb4")"; echo -n "$header" | hdtaki sam wynik.
ctrl-alt-delor
Działa to header="\x36\xc9\xda\x00\xb4"; echo -n "$header" | hd, ale nie jest tym samym, co przechowywanie postaci czytelnej dla człowieka.
ctrl-alt-delor

Odpowiedzi:

16

Nie można przechowywać bajtu zerowego w ciągu, ponieważ Bash używa ciągów w stylu C, które rezerwują bajt zerowy na terminatory. Musisz więc przepisać skrypt, aby po prostu potokować sekwencję zawierającą bajt zerowy, bez konieczności zapisywania go przez Bash na środku. Na przykład możesz to zrobić:

printf "\x36\xc9\xda\x00\xb4" | hd

Przy okazji zwróć uwagę, że nie potrzebujesz echo; możesz użyć Basha printfdo wykonania wielu innych prostych zadań.

Lub zamiast tworzenia łańcuchów możesz użyć pliku tymczasowego:

printf "\x36\xc9\xda\x00\xb4" > /tmp/mysequence
hd /tmp/mysequence

Oczywiście ma to problem polegający na tym, że plik /tmp/mysequencemoże już istnieć. A teraz musisz nadal tworzyć pliki tymczasowe i zapisywać ich ścieżki w ciągach.

Lub możesz tego uniknąć, stosując substytucję procesu:

hd <(printf "\x36\xc9\xda\x00\xb4")

<(command)Operator tworzy nazwanego potoku w systemie plików, który otrzyma wyjście command. hdotrzyma jako swój pierwszy argument ścieżkę do tego potoku - który otworzy i przeczyta prawie jak każdy plik. Możesz przeczytać więcej na ten temat tutaj: /unix//a/17117/136742 .

Giusti
źródło
1
Chociaż jest poprawny, jest to szczegół implementacji, a nie dokładny powód. Spojrzałem na to, a standard POSIX faktycznie wymaga takiego zachowania, więc masz rzeczywisty powód. (Jak niektórzy zauważyli, zshzrobię to, ale tylko w trybie nōn-POSIX). Właściwie to przyjrzałem się, ponieważ zastanawiałem się, czy warto to zaimplementować w mksh
mirabilos
@mirabilos, czy chciałbyś rozwinąć tę kwestię? AFAICT, zachowanie nie jest określone dla POSIX dla podstawiania poleceń, gdy wyjście ma znaki NUL, a dla zsh w trybie POSIX, jedyną istotną różnicą, o której mogę myśleć, jest to, że w shemulacji \0nie ma domyślnej wartości $IFS. echo "$(printf 'a\0b')"nadal działa OK w shemulacji w zsh.
Stéphane Chazelas
3
@mirabilos Biorąc pod uwagę, że powłoki wyprzedzają standard POSIX o dekadę lub dłużej, wydaje mi się, że można się dowiedzieć, że faktycznym powodem jest to, że powłoki używały ciągów w stylu C, a standard został zbudowany wokół tego.
giusti
Znalazłem dobre Q do szczegółowej dyskusji na temat printf kontra echo. unix.stackexchange.com/questions/65803/…
Paulb
8

Zamiast tego możesz użyć, zshktóra jest jedyną powłoką, która może przechowywać znak NUL w jego zmiennych. Ta postać ma nawet domyślną wartość $IFSin zsh.

nul=$'\0'

Lub:

nul=$'\x0'

Lub

nul=$'\u0000'

Lub

nul=$(printf '\0')

Należy jednak pamiętać, że nie można przekazać takiej zmiennej jako argumentu lub zmiennej środowiskowej do komendy, która jest wykonywana, ponieważ argumenty i zmienne środowiskowe są łańcuchami rozdzielanymi przez NUL przekazywanymi do execve()wywołania systemowego (ograniczenie interfejsu API systemu, a nie powłoki ). W zsh, można jednak przejść NUL bajty jako argumenty funkcji lub wbudowanych poleceń.

echo $'\0' # works
/bin/echo $'\0' # doesn't
Stéphane Chazelas
źródło
1
„Zamiast tego możesz użyć zsh”. Nie, dziękuję - uczę się teraz pisania skryptów bash. Nie chcę mylić się z inną składnią. Ale bardzo dziękuję za sugestię
Frank
W rzeczywistości użyłeś zshskładni w swoim pytaniu. echo -n $headeroznaczać przekazać zawartość $headerzmiennej jako ostatni argument echo -njest zsh(lub fishlub rclub es) składnia, nie bash składnia. W bash, który ma zupełnie inne znaczenie . Bardziej ogólnie zshjest podobny ksh( bashpowłoka GNU, będąca mniej więcej częściowo klonem ksh, de facto uniksowa powłoka Unixa), ale z naprawioną większością charakterystycznych cech projektowych powłoki Bourne'a (oraz z wieloma dodatkowymi funkcjami i wieloma bardziej przyjazny dla użytkownika / mniej zadziwiający).
Stéphane Chazelas
Uwaga: zsh może czasami zmieniać bajt zerowy: echo $(printf 'ab\0cd') | od -vAn -tx1c wypisuje `61 62 20 63 64 0a`, czyli przestrzeń, w której powinien istnieć NUL.
Izaak
1
I to jest coś, czego żadna inna (żadna, zero) powłoka nie powiela. To sprawia, że ​​skrypt zachowuje się w Zsh w bardzo szczególny sposób. Moim zdaniem: zsh próbuje być zbyt sprytny.
Izaak
2
Po „naprawieniu” błędów projektowych obecnych w standardzie POSIX sh, przyzwyczajenie się do pisania skryptów zsh oznacza, że ​​przyzwyczaja się do praktyk, które byłyby błędne, gdyby były wykonywane w jakiejkolwiek innej powłoce. Nie jest to taki problem ze składnią, która jest tak odmienna od innego języka, że ​​umiejętności lub nawyki prawdopodobnie nie zostaną przeniesione, ale tak nie jest.
Charles Duffy