Tại sao $ '\ 0' giống với ''?


10

Một cách phổ biến để thực hiện mọi thứ với một vài tệp là, và đừng đánh tôi vì điều đó:

for f in $(ls); do 

Bây giờ, để an toàn trước các tệp có dấu cách hoặc các ký tự lạ khác, một cách ngây thơ sẽ là:

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

Ở đây, -d ''viết tắt của việc đặt ASCII NUL như trong -d $'\0'.

Nhưng tại sao lại như vậy? Tại sao ''$'\0'giống nhau? Đó có phải là do gốc C của Bash với một chuỗi rỗng luôn bị chấm dứt không?


Nhắc đến cách "ngây thơ", có cách nào tốt hơn để làm việc này không?
iruvar

2
Nhân tiện, nếu bạn muốn thực hiện các thao tác an toàn lặp lại trên một tập hợp các tệp - sử dụng for f in *thay vì phân tích cú pháp ls.

@htor Tôi biết for i in $(ls)là ngu ngốc khủng khiếp. Tôi gần như xấu hổ vì tôi đã sử dụng nó như một ví dụ tồi tệ ở đây.
slhck

@ChandraRavoori Có, ví dụ: bằng cách sử dụng find … -execthay vì lặp xung quanh các tệp, hoạt động cho hầu hết các trường hợp bạn sử dụng vòng lặp for như vậy để thay thế. Ở đây, findchăm sóc tất cả mọi thứ cho bạn.
slhck

@slhck, cảm ơn. Điều gì về các tình huống liên quan đến hoạt động nhiều bước trên mỗi tệp trong đó một vòng lặp có thể thích hợp hơn vì lý do dễ đọc? Có một tùy chọn vòng lặp tốt hơn "cách ngây thơ" ở trên không?
iruvar

Câu trả lời:


10

Các man page of bashbài đọc:

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

Vì các chuỗi thường được kết thúc bằng null, ký tự đầu tiên của chuỗi rỗng là byte rỗng. - Có nghĩa với tôi. :)

Nguồn đọc:

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

Đối với một chuỗi rỗng delimchỉ đơn giản là byte rỗng.


Khi bạn nói "chuỗi thường không kết thúc", đó không phải là trường hợp ở đâu đó trong môi trường POSIX? Từ những ngày tôi học C ở trường, dĩ nhiên là có lý khi cho là như vậy; Tôi chỉ đang kiểm tra.
slhck

Nhưng người ta có thể coi bất kỳ chuỗi nào có chứa nhiều chuỗi trống tùy ý, ví dụ: nếu bạn ghép nối '' và "X", bạn nhận được "X". Vì vậy, bạn có thể lập luận rằng các chuỗi bash chuỗi con đầu tiên gặp phải là chuỗi rỗng. Ví dụ: nếu bạn sử dụng chuỗi trống trong javascript, split()nó sẽ phân chia giữa mỗi ký tự. Tôi nghi ngờ "vì lý do lịch sử" có thể là lời giải thích tốt nhất chúng ta có thể nhận được.
donothings thành công

Vâng, không hoàn toàn vì "concatenating" C-phong cách '\0'với 'X\0'nên cung cấp cho bạn 'X\0', nếu được thực hiện đúng. Điều này không liên quan nhiều đến các chức năng cấp cao trong các ngôn ngữ như JavaScript @don
slhck

Cảm ơn, michas, vì đã thêm nguồn. delim = *list_optarg;làm rõ tại sao nó lại như vậy
slhck

@slhck: Xin lỗi, tôi không nói rõ. Bạn hỏi "tại sao ''$'\0'giống nhau không?", Michas tặng Giải thích sâu xa của "đó là những gì mã lệnh thực hiện". Tôi đã phác thảo một cách khác để xử lý chuỗi trống mà tôi thấy là hợp lý như nhau và cho rằng việc chọn cái này hay cái kia chỉ đơn giản là vấn đề quy ước hoặc tình cờ.
donothings thành công

6

Có hai thiếu sót trong bash bù cho nhau.

Khi bạn viết $'\0', điều đó được xử lý nội bộ giống hệt với chuỗi rỗng. Ví dụ:

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

Đó là bởi vì bash bên trong lưu trữ tất cả các chuỗi dưới dạng các chuỗi C , được kết thúc bằng null - một byte null đánh dấu sự kết thúc của chuỗi. Bash âm thầm cắt chuỗi thành byte rỗng đầu tiên (không phải là một phần của chuỗi!).

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

Khi bạn truyền một chuỗi làm đối số cho -dtùy chọn của readnội trang, bash chỉ nhìn vào byte đầu tiên của chuỗi. Nhưng nó không thực sự kiểm tra xem chuỗi không trống. Trong nội bộ, một chuỗi rỗng được biểu diễn dưới dạng một mảng byte gồm 1 phần tử chỉ chứa một byte rỗng. Vì vậy, thay vì đọc byte đầu tiên của chuỗi, bash đọc byte null này.

Sau đó, bên trong, các máy móc phía sau tích hợp readhoạt động tốt với các byte rỗng; nó tiếp tục đọc từng byte cho đến khi tìm thấy dấu phân cách.

Các vỏ khác hoạt động khác nhau. Ví dụ, ash và ksh bỏ qua byte rỗng khi họ đọc đầu vào. Với ksh, ksh -d ""đọc cho đến khi một dòng mới. Shell được thiết kế để đối phó tốt với văn bản, không phải với dữ liệu nhị phân. Zsh là một ngoại lệ: nó sử dụng một biểu diễn chuỗi đối phó với các byte tùy ý, bao gồm các byte null; trong zsh, $'\0'là một chuỗi có độ dài 1 (nhưng read -d '', thật kỳ lạ, hoạt động như thế nào read -d $'\0').


Hành vi readthay đổi trong bash 4.3 để bây giờ bỏ qua các byte rỗng. Ví dụ read x< <(printf a\\0a)đặt xthành aathay vì a.
Lri
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.