Bagaimana saya bisa bekerja dengan biner di bash, untuk menyalin byte kata demi kata tanpa konversi?

14

Saya ambisius mencoba menerjemahkan kode c ++ ke bash karena berbagai alasan.

Kode ini membaca dan memanipulasi tipe file khusus untuk sub-bidang saya yang ditulis dan terstruktur sepenuhnya dalam biner. Tugas saya yang berhubungan dengan biner pertama adalah menyalin 988 byte pertama dari header, persis apa adanya, dan memasukkannya ke dalam file output yang saya dapat terus menulis ketika saya menghasilkan sisa informasi.

Saya cukup yakin bahwa solusi saya saat ini tidak berfungsi, dan secara realistis saya belum menemukan cara yang baik untuk menentukan ini. Jadi, bahkan jika itu benar-benar ditulis dengan benar, saya perlu tahu bagaimana saya akan menguji ini untuk memastikan!

Inilah yang sedang saya lakukan sekarang:

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

Jika saya menggunakan hexdump / xxd untuk memeriksa bagian file ini, walaupun saya tidak bisa membaca sebagian besar, ada sesuatu yang salah. Dan kode yang saya tulis sebagai perbandingan hanya memberi tahu saya jika dua string identik, bukan jika mereka disalin seperti yang saya inginkan.

Apakah ada cara yang lebih baik untuk melakukan ini di bash? Bisakah saya cukup menyalin / membaca byte biner dalam native-binary, untuk menyalin ke file kata demi kata? (dan idealnya untuk menyimpan sebagai variabel juga).

neurocoder
sumber
Anda dapat menggunakannya dduntuk menyalin masing-masing byte (mengaturnya countuntuk 1). Saya tidak yakin tentang menyimpannya.
DDPWNAGE
Jangan melakukan bash dengan cara C, itu akan membuat banyak sakit kepala. Alih-alih menggunakan konstruksi bash yang tepat
Ferrybig

Jawaban:

22

Berurusan dengan data biner pada level rendah dalam skrip shell umumnya merupakan ide yang buruk.

bashvariabel tidak dapat berisi byte 0. zshadalah satu-satunya shell yang dapat menyimpan byte itu dalam variabelnya.

Dalam kasus apa pun, argumen perintah dan variabel lingkungan tidak dapat berisi byte tersebut karena mereka adalah string yang dibatasi NUL yang diteruskan ke execvepanggilan sistem.

Perhatikan juga bahwa:

var=`cmd`

atau bentuknya yang modern:

var=$(cmd)

strip semua karakter trailing baris baru dari output dari cmd. Jadi, jika output biner itu berakhir dalam 0xa byte, itu akan hancur ketika disimpan di $var.

Di sini, Anda perlu menyimpan data yang disandikan, misalnya dengan xxd -p.

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

Anda dapat mendefinisikan fungsi pembantu seperti:

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 -poutput tidak ruang efisien karena mengkodekan 1 byte dalam 2 byte, tetapi membuatnya lebih mudah untuk melakukan manipulasi dengannya (menyatukan, mengekstraksi bagian). base64adalah salah satu yang mengkodekan 3 byte dalam 4, tetapi tidak mudah untuk dikerjakan.

The ksh93shell memiliki builtin encoding format yang (kegunaan base64) yang dapat Anda gunakan dengan nya readdan printf/ printutilitas:

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

Sekarang, jika tidak ada transit melalui variabel shell atau env, atau argumen perintah, Anda harus OK selama utilitas yang Anda gunakan dapat menangani nilai byte apa pun. Tetapi perhatikan bahwa untuk utilitas teks, sebagian besar implementasi non-GNU tidak dapat menangani byte NUL, dan Anda harus memperbaiki lokal ke C untuk menghindari masalah dengan karakter multi-byte. Karakter terakhir yang tidak menjadi karakter baris baru juga dapat menyebabkan masalah dan juga garis yang sangat panjang (urutan byte di antara dua 0xa byte yang lebih panjang LINE_MAX).

head -cdi mana itu tersedia harus OK di sini, karena itu dimaksudkan untuk bekerja dengan byte, dan tidak memiliki alasan untuk memperlakukan data sebagai teks. Begitu

head -c 988 < input > output

harus baik-baik saja. Dalam praktiknya setidaknya implementasi builtin GNU, FreeBSD dan ksh93 adalah OK. POSIX tidak menentukan -copsi, tetapi mengatakan headharus mendukung garis dengan panjang berapa pun (tidak terbatas pada LINE_MAX)

Dengan zsh:

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

Atau:

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

Bahkan di zsh, jika $varberisi byte NUL, Anda bisa meneruskannya sebagai argumen ke zshbuiltin (seperti di printatas) atau fungsi, tetapi bukan sebagai argumen untuk executable, karena argumen yang diteruskan ke executable adalah string dibatasi NUL, itu adalah batasan kernel, tidak tergantung pada shell.

Stéphane Chazelas
sumber
zshbukan satu-satunya shell yang dapat menyimpan satu atau lebih byte NUL dalam variabel shell. ksh93dapat melakukannya juga. Secara internal, ksh93cukup simpan variabel biner sebagai string yang dikodekan base64.
fpmurphy
@ fpmurphy1, bukan itu yang saya sebut penanganan data biner , variabel tidak mengandung data biner, jadi Anda tidak dapat menggunakan salah satu operator shell pada mereka misalnya, Anda tidak bisa meneruskannya ke builtin atau fungsi di dalamnya bentuk decode ... Saya akan menyebutnya dukungan base64 encoding / decoding .
Stéphane Chazelas
11

Saya ambisius mencoba menerjemahkan kode c ++ ke bash karena berbagai alasan.

Baiklah. Tapi mungkin Anda harus mempertimbangkan alasan yang sangat penting untuk TIDAK melakukannya. Pada dasarnya, "bash" / "sh" / "csh" / "ksh" dan sejenisnya tidak dirancang untuk memproses data biner, dan tidak juga sebagian besar utilitas standar UNIX / LINUX.

Anda akan lebih baik tetap dengan C ++, atau menggunakan bahasa scripting seperti Python, Ruby atau Perl yang mampu menangani data biner.

Apakah ada cara yang lebih baik untuk melakukan ini di bash?

Cara yang lebih baik adalah tidak melakukannya di bash.

Stephen C
sumber
4
+1 untuk "Cara yang lebih baik adalah tidak melakukannya di bash."
Guntram Blohm mendukung Monica
1
Alasan lain untuk tidak menggunakan rute ini adalah bahwa aplikasi yang dihasilkan akan berjalan secara signifikan lebih lambat dan mengkonsumsi lebih banyak sumber daya sistem.
fpmurphy
Bash pipelines dapat bertindak sebagai jenis bahasa domain tingkat tinggi yang dapat meningkatkan pemahaman. Ada apa-apa tentang pipa yang tidak biner, dan ada berbagai utilitas diimplementasikan sebagai alat baris perintah yang berinteraksi dengan data biner ( ffmpeg, imagemagick, dd). Sekarang jika seseorang melakukan pemrograman daripada menempelkan semuanya bersama-sama maka menggunakan bahasa pemrograman yang berdaya penuh adalah cara yang harus dilakukan.
Att Righ
6

Dari pertanyaan Anda:

salin 988 baris pertama dari header

Jika Anda menyalin 988 baris, maka sepertinya file teks, bukan biner. Namun, kode Anda tampaknya mengasumsikan 988 byte, bukan 988 baris, jadi saya akan menganggap byte benar.

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

Bagian ini mungkin tidak berfungsi. Untuk satu hal, setiap byte NUL dalam aliran akan dilucuti, karena Anda gunakan ${hdr_988}sebagai argumen baris perintah, dan argumen baris perintah tidak dapat berisi NUL. Backticks mungkin melakukan greening spasi putih juga (saya tidak yakin tentang itu). (Sebenarnya, karena echoini adalah built-in, pembatasan NUL mungkin tidak berlaku, tapi saya akan mengatakan itu masih rapuh.)

Mengapa tidak menulis header langsung dari file input ke file output, tanpa melewati variabel shell?

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

Atau, lebih nyaman,

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

Karena Anda menyebutkan Anda menggunakan bash, bukan shell POSIX, Anda memiliki substitusi proses yang tersedia untuk Anda, jadi bagaimana dengan ini sebagai tes?

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

Akhirnya: pertimbangkan untuk menggunakan $( ... )backticks.

Celada
sumber
Catatan yang ddbelum tentu setara dengan headuntuk file non-reguler. headakan melakukan read(2)panggilan sistem sebanyak yang diperlukan untuk mendapatkan 988 byte tersebut sementara ddhanya akan melakukan satu read(2). GNU ddmemiliki iflag=fullblockuntuk mencoba dan membaca blok itu secara penuh, tetapi itu bahkan lebih portabel daripada head -c.
Stéphane Chazelas