Dlaczego $ „\ 0” jest tym samym co „”?


10

Typowym sposobem robienia rzeczy z kilkoma plikami jest - i nie uderzaj mnie za to:

for f in $(ls); do 

Teraz, aby zabezpieczyć się przed plikami ze spacjami lub innymi dziwnymi postaciami, naiwnym sposobem byłoby zrobienie:

find . -type f -print0 | while IFS= read -r -d '' file; 

Tutaj -d ''skrótem jest ustawienie ASCII NUL jak w -d $'\0'.

Ale dlaczego tak jest? Dlaczego są ''i $'\0'takie same? Czy to z powodu korzeni C Bash z pustym łańcuchem zawsze kończącym się na zero?


Odnosząc się do „naiwnego” sposobu, czy jest na to lepszy sposób?
iruvar

2
Nawiasem mówiąc, jeśli chcesz wykonywać bezpieczne operacje, iterując zbiór plików - użyj for f in *zamiast parsowania ls.

@ htor Wiem, że for i in $(ls)jest strasznie głupi - prawie się wstydzę, że użyłem go tutaj jako złego przykładu.
slhck

@ChandraRavoori Tak, na przykład przez użycie find … -execzamiast zapętlania plików, co działa w większości przypadków, w których zamiast tego użyłbyś takiej pętli for. Tutaj finddba o wszystko dla Ciebie.
slhck

@slhck, dzięki. Co z sytuacjami obejmującymi wieloetapowe operacje na każdym pliku, w których pętla może być lepsza ze względu na czytelność? Czy istnieje lepsza opcja pętli niż „naiwny sposób” powyżej?
iruvar

Odpowiedzi:


10

man page of bashBrzmi:

          -d delim
                 The first character of delim is  used  to  terminate  the
                 input line, rather than newline.

Ponieważ ciągi zwykle kończą się znakiem NULL, pierwszym znakiem pustego ciągu jest bajt NULL. - Dla mnie to ma sens. :)

Źródło brzmi:

static unsigned char delim;
[...]
    case 'd':
      delim = *list_optarg;
      break;

Bo pusty ciąg delimjest po prostu bajtem zerowym.


Kiedy mówisz, że „łańcuchy są zwykle zakończone zerem”, czy nie dzieje się tak gdzieś w środowisku POSIX? Od czasów, gdy uczyłem się języka C w szkole, oczywiście warto to zakładać; Właśnie sprawdzałem.
slhck 12.01.2013

Ale można uznać, że dowolny ciąg zawiera dowolnie wiele pustych ciągów, np. Jeśli połączysz „” i „X”, otrzymasz „X”. Można więc argumentować, że pierwszym napotkanym bashem podłańcuchowym jest pusty ciąg. Na przykład, jeśli użyjesz pustego ciągu w javascript, split()zostanie on podzielony między każdy znak. Podejrzewam, że „z powodów historycznych” może być najlepszym wytłumaczeniem, jakie możemy uzyskać.
darowizny z powodzeniem

No, może nie całkiem, bo „złączenie” C-Styl '\0'ze 'X\0'powinno dać wam 'X\0', jeśli zrobione prawo. Nie ma to wiele wspólnego z funkcjami wysokiego poziomu w językach takich jak JavaScript @don
slhck 12.01.2013

Dzięki, michas, za dodanie źródła. delim = *list_optarg;wyjaśnia, dlaczego tak jest.
slhck 12.01.2013

@slhck: Przepraszam, nie wyraziłem się jasno. Zapytałeś „dlaczego ''i $'\0'to samo?”, Michas podał przybliżone wyjaśnienie „właśnie to robi kod”. Przedstawiłem alternatywny sposób postępowania z pustym ciągiem, który uważałem za równie rozsądny, i zasugerowałem, że wybranie jednego lub drugiego jest po prostu kwestią konwencji lub zdarzenia.
darowizny z powodzeniem

6

Istnieją dwa braki w bash, które wzajemnie się kompensują.

Kiedy piszesz $'\0', jest to traktowane wewnętrznie identycznie jak pusty ciąg. Na przykład:

$ a=$'\0'; echo ${#a}
0

To dlatego, że wewnętrznie bash sklepach wszystkie ciągi jako C ciągów, które są zakończony zerem - A znaki NUL koniec łańcucha. Bash po cichu obcina łańcuch do pierwszego bajtu zerowego (który nie jest częścią łańcucha!).

# a=$'foo\0bar'; echo "$a"; echo ${#a}
foo
3

Kiedy przekazujesz ciąg jako argument do -dopcji readwbudowanej, bash patrzy tylko na pierwszy bajt ciągu. Ale tak naprawdę nie sprawdza, czy ciąg nie jest pusty. Wewnętrznie pusty ciąg jest reprezentowany jako 1-elementowa tablica bajtów, która zawiera tylko bajt zerowy. Zamiast czytać pierwszy bajt łańcucha, bash czyta ten bajt zerowy.

Następnie wewnętrznie mechanizm readwbudowany działa dobrze z bajtami zerowymi; ciągle odczytuje bajt po bajcie, dopóki nie znajdzie separatora.

Inne pociski zachowują się inaczej. Na przykład ash i ksh ignorują bajty zerowe podczas odczytu danych wejściowych. Z ksh ksh -d ""czyta do nowej linii. Muszle są zaprojektowane tak, aby dobrze radziły sobie z tekstem, a nie z danymi binarnymi. Zsh jest wyjątkiem: używa reprezentacji łańcuchowej, która radzi sobie z dowolnymi bajtami, w tym bajtami zerowymi; w zsh $'\0'jest łańcuchem o długości 1 (ale read -d ''dziwnie zachowuje się jak read -d $'\0').


Zachowanie readzmienione w wersji bash 4.3, tak że teraz pomija bajty zerowe. Na przykład read x< <(printf a\\0a)ustawia xna aazamiast a.
Lri
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.