Làm cách nào để sử dụng byte rỗng trong Bash?


33

Tôi đã đọc điều đó, vì các đường dẫn tệp trong Bash có thể chứa bất kỳ ký tự nào ngoại trừ byte null (byte có giá trị bằng 0 $'\0'), rằng tốt nhất nên sử dụng byte null làm dấu tách. Ví dụ: nếu đầu ra của findsẽ được gửi đến một chương trình khác, bạn nên sử dụng -print0tùy chọn (đối với các phiên bản findcó nó).

Nhưng mặc dù một cái gì đó như thế này hoạt động tốt (in đường dẫn tệp được phân tách bằng dòng mới - đừng lo lắng, đây chỉ là một minh chứng, tôi thực sự không làm điều đó trong các tập lệnh thực):

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

một cái gì đó như thế này không hoạt động:

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

Khi tôi thử chỉ phần for-loop, tôi thấy rằng nó chỉ in tất cả các tên tệp với nhau, không có byte rỗng ở giữa.

Tại sao lại thế này? Chuyện gì đang xảy ra vậy?

Câu trả lời:


43

Bash sử dụng các chuỗi kiểu C bên trong, được kết thúc bằng các byte rỗng. Điều này có nghĩa là một chuỗi Bash (chẳng hạn như giá trị của biến hoặc đối số cho lệnh) thực sự không bao giờ có thể chứa byte rỗng. Ví dụ: tập lệnh nhỏ này:

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

thực sự in 3, bởi vì $foobarthực sự chỉ là 'foo': barđến sau khi kết thúc chuỗi.

Tương tự, echo $'foo\0bar'chỉ in foo, bởi vìecho không biết về \0barphần này.

Như bạn có thể thấy, \0chuỗi thực sự rất sai lệch trong $'...'chuỗi kiểu; nó trông giống như một byte rỗng bên trong chuỗi, nhưng nó không hoạt động theo cách đó. Trong ví dụ đầu tiên của bạn, readlệnh của bạn có -d $'\0'. Điều này hoạt động, nhưng chỉ vì -d ''cũng hoạt động! (Đó không phải là một tính năng ghi nhận một cách rõ ràng về read, nhưng tôi cho rằng nó hoạt động với cùng lý do: ''là chuỗi rỗng, vì vậy chấm dứt vô byte của nó đến ngay lập tức. Là tài liệu như sử dụng "Ký tự đầu tiên của dấu phân cách ", và tôi đoán rằng tác phẩm thậm chí nếu "ký tự đầu tiên" ở cuối chuỗi!)-d delim

Nhưng như bạn đã biết từ của bạn findVí dụ, nó có thể cho một lệnh để in ra một byte null, và cho byte đó để được đường ống để một lệnh mà đọc nó như là đầu vào. Không có phần nào trong đó phụ thuộc vào việc lưu trữ một byte null trong một chuỗi bên trong Bash . Vấn đề duy nhất với ví dụ thứ hai của bạn là chúng tôi không thể sử dụng$'\0' trong một đối số cho một lệnh; echo "$file"$'\0'có thể vui vẻ in byte null ở cuối, nếu chỉ có nó biết rằng bạn muốn nó.

Vì vậy, thay vì sử dụng echo, bạn có thể sử dụng printf, hỗ trợ các chuỗi thoát tương tự như $'...'chuỗi kiểu. Bằng cách đó, bạn có thể in một byte null mà không cần phải có byte rỗng bên trong chuỗi. Điều đó sẽ trông như thế này:

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

hoặc đơn giản là thế này:

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

(Lưu ý: echothực sự cũng có một -ecờ cho phép nó xử lý \0và in một byte null; nhưng sau đó nó cũng sẽ cố gắng xử lý bất kỳ chuỗi đặc biệt nào trong tên tệp của bạn.printf cách tiếp cận mạnh mẽ hơn.)


Ngẫu nhiên, có một số vỏ mà làm phép null byte chuỗi bên trong. Ví dụ của bạn hoạt động tốt trong Zsh, ví dụ (giả sử cài đặt mặc định). Tuy nhiên, bất kể hệ vỏ của bạn là gì, các hệ điều hành giống Unix không cung cấp cách bao gồm các byte rỗng bên trong các đối số cho các chương trình (vì các đối số chương trình được truyền dưới dạng chuỗi kiểu C), do đó sẽ luôn có một số hạn chế. (Ví dụ của bạn chỉ có thể hoạt động trong Zsh vì echođược tích hợp shell, vì vậy Zsh có thể gọi nó mà không cần dựa vào sự hỗ trợ của HĐH để gọi các chương trình khác. Nếu bạn sử dụng command echothay vì echo, nó đã bỏ qua phần dựng sẵn và sử dụng echochương trình độc lập trên $PATH, bạn sẽ thấy hành vi tương tự trong Zsh như trong Bash.)


2
Tại sao IFS được đặt thành không có gì nếu -d ''đã có nghĩa là phân định \0? Tôi tìm thấy một lời giải thích ở đây: stackoverflow.com/questions/8677546/
Mạnh
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.