Làm cách nào tôi có thể làm việc với nhị phân trong bash, để sao chép nguyên văn byte mà không cần chuyển đổi?


14

Tôi tham vọng cố gắng dịch mã c ++ thành bash vì vô số lý do.

Mã này đọc và thao tác một loại tệp cụ thể cho trường con của tôi được viết và cấu trúc hoàn toàn dưới dạng nhị phân. Nhiệm vụ liên quan đến nhị phân đầu tiên của tôi là sao chép 988 byte đầu tiên của tiêu đề, chính xác, và đưa chúng vào một tệp đầu ra mà tôi có thể tiếp tục ghi vào khi tôi tạo phần còn lại của thông tin.

Tôi khá chắc chắn rằng giải pháp hiện tại của tôi không hoạt động, và thực tế tôi đã không tìm ra một cách tốt để xác định điều này. Vì vậy, ngay cả khi nó thực sự được viết chính xác, tôi cần biết làm thế nào tôi sẽ kiểm tra điều này để chắc chắn!

Đây là những gì tôi đang làm ngay bây giờ:

hdr_988=`head -c 988 ${inputFile}`
echo -n "${hdr_988}" > ${output_hdr}
headInput=`head -c 988 ${inputTrack} | hexdump`
headOutput=`head -c 988 ${output_hdr} | hexdump`
if [ "${headInput}" != "${headOutput}" ]; then echo "output header was not written properly.  exiting.  please troubleshoot."; exit 1; fi

Nếu tôi sử dụng hexdump / xxd để kiểm tra phần này của tệp, mặc dù tôi không thể đọc chính xác phần lớn của nó, có vẻ như đã sai. Và mã tôi đã viết để so sánh chỉ cho tôi biết nếu hai chuỗi giống hệt nhau, chứ không phải nếu chúng được sao chép theo cách tôi muốn.

Có cách nào tốt hơn để làm điều này trong bash? Tôi có thể chỉ cần sao chép / đọc byte nhị phân trong tệp nhị phân gốc, để sao chép vào nguyên văn tệp không? (và lý tưởng để lưu trữ như các biến là tốt).


Bạn có thể sử dụng ddđể sao chép các byte cá nhân (thiết lập của nó countđể 1). Tôi không chắc chắn về việc lưu trữ chúng, mặc dù.
DDPWNAGE

Đừng làm bash theo cách C, nó sẽ tạo ra nhiều vấn đề đau đầu. Thay vào đó, hãy sử dụng các cấu trúc bash thích hợp
Ferrybig

Câu trả lời:


22

Xử lý dữ liệu nhị phân ở mức thấp trong các kịch bản shell nói chung là một ý tưởng tồi.

bashcác biến không thể chứa byte 0. zshlà lớp vỏ duy nhất có thể lưu trữ byte đó trong các biến của nó.

Trong mọi trường hợp, các đối số lệnh và biến môi trường không thể chứa các byte đó vì chúng là các chuỗi phân cách NUL được truyền cho lệnh execvegọi hệ thống.

Cũng lưu ý rằng:

var=`cmd`

hoặc hình thức hiện đại của nó:

var=$(cmd)

dải tất cả các ký tự dòng mới từ đầu ra của cmd. Vì vậy, nếu đầu ra nhị phân đó kết thúc bằng 0xa byte, nó sẽ được xử lý khi được lưu trữ $var.

Ở đây, bạn cần lưu trữ dữ liệu được mã hóa, ví dụ với xxd -p.

hdr_988=$(head -c 988 < "$inputFile" | xxd -p)
printf '%s\n' "$hdr_988" | xxd -p -r > "$output_hdr"

Bạn có thể định nghĩa các hàm trợ giúp như:

encode() {
  eval "$1"='$(
    shift
    "$@" | xxd -p  -c 0x7fffffff
    exit "${PIPESTATUS[0]}")'
}

decode() {
  printf %s "$1" | xxd -p -r
}

encode var cat /bin/ls &&
  decode "$var" | cmp - /bin/ls && echo OK

xxd -pđầu ra không phải là không gian hiệu quả vì nó mã hóa 1 byte thành 2 byte, nhưng nó giúp thực hiện các thao tác với nó dễ dàng hơn (nối, trích xuất các phần). base64là một mã hóa 3 byte trong 4, nhưng không dễ để làm việc với.

Các ksh93vỏ có BUILTIN mã hóa định dạng (sử dụng base64) mà bạn có thể sử dụng với nó readprintf/ printtiện ích:

typeset -b var # marked as "binary"/"base64-encoded"
IFS= read -rn 988 var < input
printf %B var > output

Bây giờ, nếu không có chuyển đổi qua các biến shell hoặc env hoặc đối số lệnh, bạn sẽ ổn miễn là các tiện ích bạn sử dụng có thể xử lý bất kỳ giá trị byte nào. Nhưng lưu ý rằng đối với các tiện ích văn bản, hầu hết các triển khai không phải GNU không thể xử lý các byte NUL và bạn sẽ muốn sửa ngôn ngữ thành C để tránh các vấn đề với các ký tự nhiều byte. Ký tự cuối cùng không phải là ký tự dòng mới cũng có thể gây ra vấn đề cũng như các dòng rất dài (chuỗi byte ở giữa hai byte 0xa dài hơn đó LINE_MAX).

head -cở đâu có sẵn thì sẽ ổn ở đây, vì nó có nghĩa là hoạt động với byte và không có lý do gì để coi dữ liệu là văn bản. Vì thế

head -c 988 < input > output

nên ổn Trong thực tế ít nhất các triển khai dựng sẵn GNU, FreeBSD và ksh93 đều ổn. POSIX không chỉ định -ctùy chọn, nhưng cho biết headnên hỗ trợ các dòng có độ dài bất kỳ (không giới hạn LINE_MAX)

Với zsh:

IFS= read -rk988 -u0 var < input &&
print -rn -- $var > output

Hoặc là:

var=$(head -c 988 < input && echo .) && var=${var%.}
print -rn -- $var > output

Ngay cả trong zsh, nếu $varchứa byte NUL, bạn có thể chuyển nó dưới dạng đối số cho các hàm dựng zsh(như printở trên) hoặc các hàm, nhưng không phải là đối số cho các tệp thực thi, vì các đối số được truyền cho các tệp thực thi là các chuỗi phân cách NUL, đó là giới hạn kernel, không phụ thuộc vào shell.


zshkhông phải là shell duy nhất có thể lưu trữ một hoặc nhiều byte NUL trong một biến shell. ksh93cũng có thể làm như vậy. Trong nội bộ, ksh93chỉ cần lưu trữ biến nhị phân dưới dạng chuỗi được mã hóa base64.
fpmurphy

@ fpmurphy1, đó không phải là cái mà tôi gọi là xử lý dữ liệu nhị phân , biến không chứa dữ liệu nhị phân, vì vậy bạn không thể sử dụng bất kỳ toán tử shell nào trên chúng, bạn không thể chuyển chúng sang hàm dựng hoặc hàm trong nó hình thức giải mã ... Tôi gọi nó là hỗ trợ mã hóa / giải mã cơ sở dựng sẵn .
Stéphane Chazelas

11

Tôi tham vọng cố gắng dịch mã c ++ thành bash vì vô số lý do.

Vâng vâng. Nhưng có lẽ bạn nên xem xét một lý do rất quan trọng để KHÔNG làm điều đó. Về cơ bản, "bash" / "sh" / "csh" / "ksh" và những thứ tương tự không được thiết kế để xử lý dữ liệu nhị phân và hầu hết các tiện ích UNIX / LINUX tiêu chuẩn.

Bạn sẽ tốt hơn nếu gắn bó với C ++ hoặc sử dụng ngôn ngữ script như Python, Ruby hoặc Perl có khả năng xử lý dữ liệu nhị phân.

Có cách nào tốt hơn để làm điều này trong bash?

Cách tốt hơn là không làm điều đó trong bash.


4
+1 cho "Cách tốt hơn là không làm điều đó trong bash."
Guntram Blohm hỗ trợ Monica

1
Một lý do khác để không đi theo con đường này là ứng dụng kết quả sẽ chạy chậm hơn đáng kể và tiêu tốn nhiều tài nguyên hệ thống hơn.
fpmurphy

Bash pipelines có thể hoạt động như một ngôn ngữ cụ thể của miền cấp cao có thể làm tăng sự dễ hiểu. Có gì về một đường ống dẫn đó không phải là nhị phân là gì, và có những tiện ích khác nhau thực hiện như các công cụ dòng lệnh mà tương tác với dữ liệu nhị phân ( ffmpeg, imagemagick, dd). Bây giờ nếu một người đang làm lập trình thay vì dán các thứ lại với nhau thì sử dụng ngôn ngữ lập trình được hỗ trợ đầy đủ là cách tốt nhất.
Att Righ

6

Từ câu hỏi của bạn:

sao chép 988 dòng đầu tiên của tiêu đề

Nếu bạn đang sao chép 988 dòng, thì nó có vẻ giống như một tệp văn bản, không phải là nhị phân. Tuy nhiên, mã của bạn dường như giả sử 988 byte, không phải 988 dòng, vì vậy tôi sẽ giả sử byte là chính xác.

hdr_988=`head -c 988 ${inputFile}`
echo -n "${hdr_988}" > ${output_hdr}

Phần này có thể không hoạt động. Đối với một điều, bất kỳ byte NUL nào trong luồng sẽ bị tước, bởi vì bạn sử dụng ${hdr_988}làm đối số dòng lệnh và đối số dòng lệnh không thể chứa NUL. Các backticks cũng có thể thực hiện việc trộn khoảng trắng (tôi không chắc về điều đó). (Trên thực tế, vì echođược tích hợp sẵn, hạn chế NUL có thể không được áp dụng, nhưng tôi sẽ nói đó vẫn là iffy.)

Tại sao không chỉ viết tiêu đề trực tiếp từ tệp đầu vào sang tệp đầu ra mà không chuyển qua biến shell?

head -c 988 "${inputFile}" >"${output_hdr}"

Hoặc, quan trọng hơn,

dd if="${inputFile}" of="${output_hdr}" bs=988 count=1

Vì bạn đề cập đến việc bạn đang sử dụng bash, không phải vỏ POSIX, bạn có sẵn quy trình thay thế cho mình, vậy làm thế nào về việc này như là một thử nghiệm?

cmp <(head -c 988 "${inputFile}") <(head -c 988 "${output_hdr}")

Cuối cùng: xem xét sử dụng $( ... )thay vì backticks.


Lưu ý rằng ddkhông nhất thiết phải tương đương với headcác tệp không thường xuyên. headsẽ thực hiện càng nhiều read(2)cuộc gọi hệ thống khi cần thiết để có được 988 byte đó trong khi ddsẽ thực hiện một cuộc gọi read(2). GNU ddiflag=fullblockđể thử và đọc khối đầy đủ, nhưng đó là sau đó thậm chí còn ít di động hơn head -c.
Stéphane Chazelas
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.