ฉันจะทำงานกับไบนารีใน bash เพื่อคัดลอกไบต์คำต่อคำโดยไม่มีการแปลงได้อย่างไร


14

ฉันพยายามแปลรหัส c ++ เป็น bash อย่างทะเยอทะยานด้วยเหตุผลมากมาย

รหัสนี้อ่านและจัดการไฟล์ประเภทเฉพาะสำหรับฟิลด์ย่อยของฉันที่เขียนและจัดโครงสร้างอย่างสมบูรณ์ในไบนารี งานที่เกี่ยวข้องกับไบนารีครั้งแรกของฉันคือการคัดลอกส่วนหัว 988 ไบต์แรกตามที่เป็นจริงและใส่ลงในไฟล์เอาต์พุตที่ฉันสามารถเขียนต่อไปได้ในขณะที่ฉันสร้างข้อมูลที่เหลือ

ฉันค่อนข้างมั่นใจว่าโซลูชันปัจจุบันของฉันใช้งานไม่ได้และในความเป็นจริงฉันไม่ได้คิดวิธีที่ดีในการพิจารณาสิ่งนี้ ดังนั้นแม้ว่ามันจะถูกเขียนขึ้นอย่างถูกต้องฉันต้องรู้ว่าฉันจะทดสอบสิ่งนี้อย่างไรเพื่อให้แน่ใจ!

นี่คือสิ่งที่ฉันกำลังทำอยู่ตอนนี้:

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

หากฉันใช้ hexdump / xxd เพื่อดูส่วนนี้ของไฟล์แม้ว่าฉันจะไม่สามารถอ่านได้เกือบทั้งหมด แต่ดูเหมือนว่ามีบางอย่างผิดปกติ และรหัสที่ฉันเขียนเพื่อเปรียบเทียบจะบอกฉันว่าสองสายเหมือนกันไม่ใช่ถ้าพวกเขาถูกคัดลอกในแบบที่ฉันต้องการ

มีวิธีที่ดีกว่านี้ในการทุบตี? ฉันสามารถคัดลอก / อ่านไบนารีไบท์ในเนทีฟไบนารี่เพื่อคัดลอกไปยังไฟล์คำต่อคำได้หรือไม่? (และเหมาะสำหรับการจัดเก็บเป็นตัวแปรเช่นกัน)


คุณสามารถใช้ddเพื่อคัดลอกไบต์ของแต่ละบุคคล (การตั้งค่าของมันcountไป1) ฉันไม่แน่ใจเกี่ยวกับการจัดเก็บพวกเขาแม้ว่า
DDPWNAGE

อย่าทุบตีในแบบ C มันจะสร้างอาการปวดหัวมากมาย ใช้โครงสร้างทุบตีที่เหมาะสมแทน
Ferrybig

คำตอบ:


22

การจัดการกับข้อมูลไบนารีในระดับต่ำในเชลล์สคริปต์โดยทั่วไปแล้วถือว่าเป็นความคิดที่ไม่ดี

bashตัวแปรไม่สามารถมีไบต์ 0 zshเป็นเชลล์เดียวที่สามารถเก็บไบต์นั้นไว้ในตัวแปร

ในกรณีใด ๆ อาร์กิวเมนต์คำสั่งและตัวแปรสภาพแวดล้อมไม่สามารถมีไบต์เหล่านั้นได้เนื่องจากเป็นสตริงที่คั่นด้วย NUL ที่ส่งผ่านไปยังการexecveเรียกของระบบ

โปรดทราบว่า:

var=`cmd`

หรือรูปแบบที่ทันสมัย:

var=$(cmd)

cmdแถบทุกตัวอักษรขึ้นบรรทัดใหม่จากการส่งออกของ ดังนั้นถ้าว่าไบนารีปลายผลลัพธ์ใน 0xA ไบต์มันจะถูก mangled $varเมื่อเก็บไว้ใน

xxd -pที่นี่คุณจะต้องเก็บข้อมูลการเข้ารหัสเช่นกับ

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

คุณสามารถกำหนดฟังก์ชั่นตัวช่วยเช่น:

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เอาต์พุตไม่มีพื้นที่ว่างที่มีประสิทธิภาพเนื่องจากมันเข้ารหัส 1 ไบต์ใน 2 ไบต์ แต่มันทำให้ง่ายต่อการจัดการกับมัน (การต่อเชื่อมแยกส่วนต่าง ๆ ) base64เป็นหนึ่งที่เข้ารหัส 3 ไบต์ใน 4 แต่ไม่ใช่เรื่องง่ายที่จะทำงานกับ

ksh93เปลือกมีในตัวเข้ารหัสรูปแบบ (ใช้base64) ซึ่งคุณสามารถใช้กับreadและprintf/ printสาธารณูปโภค:

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

ตอนนี้หากไม่มีการส่งผ่านตัวแปร shell หรือ env หรืออาร์กิวเมนต์คำสั่งคุณควรตกลงตราบใดที่ยูทิลิตี้ที่คุณใช้สามารถจัดการค่าไบต์ใด ๆ แต่โปรดทราบว่าสำหรับยูทิลิตี้ข้อความการใช้งานที่ไม่ใช่ของ GNU ส่วนใหญ่ไม่สามารถจัดการ NUL ไบต์ได้และคุณจะต้องแก้ไขโลแคลเป็น C เพื่อหลีกเลี่ยงปัญหาเกี่ยวกับอักขระหลายไบต์ อักขระตัวสุดท้ายที่ไม่ใช่อักขระขึ้นบรรทัดใหม่ยังสามารถทำให้เกิดปัญหาเช่นเดียวกับบรรทัดที่ยาวมาก (ลำดับของไบต์ระหว่าง 0xa สองไบต์ที่ยาวกว่านั้นLINE_MAX)

head -cตำแหน่งที่พร้อมใช้งานควรเป็น OK ที่นี่เนื่องจากมีไว้เพื่อทำงานกับไบต์และไม่มีเหตุผลที่จะถือว่าข้อมูลเป็นข้อความ ดังนั้น

head -c 988 < input > output

ควรจะตกลง ในทางปฏิบัติอย่างน้อย GNU, การใช้งาน FreeBSD และ ksh93 ในตัวก็โอเค POSIX ไม่ได้ระบุ-cตัวเลือก แต่บอกว่าheadควรสนับสนุนบรรทัดที่มีความยาวใด ๆ (ไม่ จำกัด เฉพาะLINE_MAX)

ด้วยzsh:

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

หรือ:

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

แม้ในzshถ้า$varมี NUL ไบต์คุณสามารถส่งมันเป็นอาร์กิวเมนต์ไปยังzshbuiltins (เช่นprintด้านบน) หรือฟังก์ชั่น แต่ไม่เป็นอาร์กิวเมนต์สำหรับ executables เนื่องจากอาร์กิวเมนต์ที่ส่งไปยัง executables เป็นสตริงที่คั่นด้วย NUL นั่นคือข้อ จำกัด เคอร์เนลซึ่งเป็นอิสระจากเชลล์


zshไม่ใช่เชลล์เท่านั้นที่สามารถเก็บ NUL หนึ่งไบต์หรือมากกว่าในตัวแปรเชลล์ ksh93สามารถทำได้เช่นกัน ภายในksh93เพียงแค่เก็บตัวแปรไบนารีเป็นสตริงที่เข้ารหัสแบบ 64
fpmurphy

@ fpmurphy1 นั่นไม่ใช่สิ่งที่ฉันเรียกว่าจัดการข้อมูลไบนารีตัวแปรไม่มีข้อมูลไบนารีดังนั้นคุณจึงไม่สามารถใช้ตัวดำเนินการเชลล์ใด ๆ กับพวกมันได้เช่นคุณไม่สามารถส่งต่อไปยัง builtins หรือฟังก์ชั่นได้ แบบฟอร์มการถอดรหัส ... ฉันเรียกมันค่อนข้างbuiltin base64 เข้ารหัส / ถอดรหัสการสนับสนุน
Stéphane Chazelas

11

ฉันพยายามแปลรหัส c ++ เป็น bash อย่างทะเยอทะยานด้วยเหตุผลมากมาย

ใช่แล้ว แต่บางทีคุณควรพิจารณาเหตุผลที่สำคัญมากสำหรับการไม่ทำ โดยพื้นฐานแล้ว "bash" / "sh" / "csh" / "ksh" และสิ่งที่คล้ายกันนั้นไม่ได้ถูกออกแบบมาสำหรับการประมวลผลข้อมูลไบนารีและไม่ได้เป็นยูทิลิตี้ UNIX / LINUX มาตรฐานส่วนใหญ่

คุณจะดีกว่าไม่ว่าจะติดกับ C ++ หรือใช้ภาษาสคริปต์เช่น Python, Ruby หรือ Perl ที่มีความสามารถในการจัดการกับข้อมูลไบนารี

มีวิธีที่ดีกว่านี้ในการทุบตี?

วิธีที่ดีกว่าคือการไม่ทำอย่างนั้น


4
+1 สำหรับ "วิธีที่ดีกว่าคือไม่ต้องทุบตี"
Guntram Blohm สนับสนุนโมนิก้า

1
อีกสาเหตุที่ไม่ควรไปตามเส้นทางนี้คือแอปพลิเคชันผลลัพธ์จะทำงานช้าลงอย่างมากและใช้ทรัพยากรระบบมากขึ้น
fpmurphy

ไปป์ไลน์ Bash สามารถทำหน้าที่เป็นภาษาเฉพาะโดเมนระดับสูงที่สามารถเพิ่มความเข้าใจได้ ไม่มีอะไรที่เกี่ยวกับท่อที่ไม่ได้เป็นไบนารีเป็นและมีสาธารณูปโภคต่างๆนำมาใช้เป็นเครื่องมือบรรทัดคำสั่งที่ทำงานกับข้อมูลไบนารี ( ffmpeg, imagemagick, dd) ทีนี้ถ้าใครคนหนึ่งกำลังเขียนโปรแกรมแทนที่จะรวมสิ่งต่าง ๆ เข้าด้วยกันการใช้ภาษาโปรแกรมที่ขับเคลื่อนเต็มรูปแบบคือหนทาง
Att Righ

6

จากคำถามของคุณ:

คัดลอก 988 บรรทัดแรกของส่วนหัว

หากคุณกำลังคัดลอก 988 บรรทัดดูเหมือนว่าจะเป็นไฟล์ข้อความไม่ใช่ไบนารี อย่างไรก็ตามรหัสของคุณดูเหมือนจะถือว่า 988 ไบต์ไม่ใช่ 988 บรรทัดดังนั้นฉันจะสมมติว่าไบต์นั้นถูกต้อง

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

ส่วนนี้อาจไม่ทำงาน สำหรับสิ่งหนึ่งไบต์ NUL ใด ๆ ในสตรีมจะถูกปล้นเนื่องจากคุณใช้${hdr_988}เป็นอาร์กิวเมนต์บรรทัดคำสั่งและอาร์กิวเมนต์บรรทัดคำสั่งไม่สามารถมี NUL ได้ Backticks อาจกำลังทำช่องว่างออกไปด้วย (ฉันไม่แน่ใจเกี่ยวกับเรื่องนั้น) (อันที่จริงแล้วเนื่องจากechoเป็นแบบในตัวข้อ จำกัด NUL อาจไม่ได้ใช้ แต่ฉันจะบอกว่ามันยังไม่แน่นอน)

ทำไมไม่เพียงเขียนส่วนหัวโดยตรงจากไฟล์อินพุตไปยังไฟล์เอาต์พุตโดยไม่ต้องส่งผ่านตัวแปรเชลล์

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

หรือพกพาสะดวกกว่า

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

เนื่องจากคุณพูดถึงว่าคุณกำลังใช้bashไม่ใช่ POSIX เชลล์คุณมีกระบวนการทดแทนสำหรับคุณดังนั้นการทดสอบเป็นอย่างไร

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

ในที่สุด: พิจารณาใช้$( ... )แทน backticks


โปรดทราบว่าddไม่จำเป็นต้องเทียบเท่ากับheadไฟล์ที่ไม่ปกติ headจะทำตามที่หลายread(2)สายระบบตามความจำเป็นเพื่อให้ได้ผู้ที่ 988 ไบต์ขณะที่ก็จะทำอย่างใดอย่างหนึ่งdd read(2)GNU ddมีiflag=fullblockและพยายามอ่านบล็อกว่าในเต็มรูปแบบ head -cแต่ที่แล้วแม้แต่น้อยกว่าแบบพกพา
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.