Jak używać bajtów zerowych w Bash?


33

Przeczytałem, że ponieważ ścieżki plików w Bash mogą zawierać dowolny znak oprócz bajtu zerowego (bajt o wartości zerowej $'\0'), najlepiej użyć bajtu zerowego jako separatora. Na przykład, jeśli dane wyjściowe findzostaną wysłane do innego programu, zaleca się użycie -print0opcji (dla wersji find, które ją mają).

Ale chociaż coś takiego działa dobrze (drukowanie ścieżek plików oddzielonych znakami nowej linii - nie martw się, to tylko demonstracja, tak naprawdę nie robię tego w prawdziwych skryptach):

find -print0 \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

coś takiego nie działa:

for file in * ; do echo -n "$file"$'\0' ; done \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

Kiedy próbuję tylko forczęści -loop, okazuje się, że po prostu drukuje wszystkie nazwy plików razem, bez bajtu zerowego pomiędzy nimi.

Dlaczego to? Co się dzieje?

Odpowiedzi:


43

Bash korzysta wewnętrznie z ciągów w stylu C, które kończą się bajtami zerowymi. Oznacza to, że ciąg Bash (taki jak wartość zmiennej lub argument polecenia) nigdy nie może zawierać bajtu zerowego. Na przykład ten mini-skrypt:

foobar=$'foo\0bar'    # foobar='foo' + null byte + 'bar'
echo "${#foobar}"     # print length of $foobar

faktycznie drukuje 3, bo $foobartak naprawdę jest 'foo': barprzychodzi po końcu łańcucha.

Podobnie, echo $'foo\0bar'po prostu drukuje foo, ponieważ echonie wie o \0barczęści.

Jak widać, \0sekwencja jest w rzeczywistości bardzo myląca w $'...'łańcuchu w stylu; wygląda jak bajt zerowy w ciągu, ale nie działa w ten sposób. W pierwszym przykładzie twoje readpolecenie ma -d $'\0'. To działa, ale tylko dlatego, że -d ''działa! (To nie jest wyraźnie udokumentowana cecha read, ale przypuszczam, że działa z tego samego powodu: ''jest pustym łańcuchem, więc jego końcowy bajt zerowy pojawia się natychmiast. Jest udokumentowany jako „Pierwszy znak delim ” i myślę, że nawet działa jeśli „pierwszy znak” znajduje się poza końcem ciągu!)-d delim

Ale jak wiadomo z findprzykładu, to jest możliwe, polecenie, aby wydrukować zerowy bajt, a do tego bajt być wyprowadzony do innego polecenia, które odczytuje go jako wejście. Żadna część tego nie polega na przechowywaniu pustego bajtu w ciągu wewnątrz Bash . Jedynym problemem związanym z drugim przykładem jest to, że nie możemy użyć $'\0'argumentu do polecenia; echo "$file"$'\0'mógłby z radością wydrukować bajt zerowy na końcu, gdyby tylko wiedział, że tego chcesz.

Zamiast używać echo, możesz użyć printf, który obsługuje takie same sekwencje specjalne jak $'...'łańcuchy-style. W ten sposób możesz wydrukować bajt zerowy bez konieczności umieszczania bajtu zerowego w ciągu. To by wyglądało tak:

for file in * ; do printf '%s\0' "$file" ; done \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

lub po prostu to:

printf '%s\0' * \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

(Uwaga: w echorzeczywistości ma także -eflagę, która pozwoli mu przetworzyć \0i wydrukować bajt zerowy; ale wtedy spróbuje również przetworzyć specjalne sekwencje w nazwie pliku. Więc printfpodejście jest bardziej niezawodne).


Nawiasem mówiąc, istnieje kilka powłok, które dopuszczają bajty puste w łańcuchach. Twój przykład działa dobrze w Zsh, na przykład (przy domyślnych ustawieniach). Jednak, niezależnie od powłoki, systemy operacyjne uniksowe nie zapewniają sposobu na umieszczenie pustych bajtów wewnątrz argumentów w programach (ponieważ argumenty programu są przekazywane jako ciągi w stylu C), więc zawsze będą pewne ograniczenia. (Twój przykład może działać w Zsh tylko dlatego, że echojest wbudowany w powłokę, więc Zsh może go wywoływać bez polegania na wsparciu systemu operacyjnego w przypadku wywoływania innych programów. Jeśli użyłeś command echozamiast tego echo, aby ominął wbudowane i używał samodzielnego echoprogramu w $PATH, zobaczysz to samo zachowanie w Zsh jak w Bash.)


2
Dlaczego IFS jest ustawiony na nic, jeśli -d ''już oznacza ograniczenie \0? Znalazłem tu wyjaśnienie: stackoverflow.com/questions/8677546/…
CMCDragonkai
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.