ไม่สามารถไปที่ "mapfile" ของทุบตี ... แต่ทำไม?


11

ฉันต้องการรับไฟล์ทั้งหมดในไดเรกทอรีที่แน่นอนใน bash array (สมมติว่าไม่มีไฟล์ใดมี newline ในชื่อ):

ดังนั้น:

myarr=()
find . -maxdepth 1  -name "mysqldump*" | mapfile -t myarr; echo "${myarr[@]}"

ผลลัพธ์ว่างเปล่า!

ถ้าฉันใช้วงเวียนในการใช้ไฟล์ชั่วคราวหรืออย่างอื่น:

myarr=()
find . -maxdepth 1  -name "mysqldump*" > X
mapfile -t myarray < X
echo "${myarray[@]}"

ผลลัพธ์!

แต่ทำไมไม่mapfileอ่านอย่างถูกต้องจากไปป์?



คำตอบที่ยอดเยี่ยมทุกรอบขอบคุณทุกคน น่าสนใจว่ากลยุทธ์การดำเนินการของไปป์ไลน์ (แต่ละส่วนทำงานในกระบวนการ spearate) รั่ว "ขึ้น" และแก้ไขความหมายที่ชัดเจนของโค้ดโดยทั่วไปแล้ววาง "local" ไว้ด้านหน้าของตัวแปรทุกตัวที่ปรากฏในไพพ์ ในภาษาที่เป็นสิ่งอื่นที่ไม่ใช่กาวบ้าคลั่งสำหรับโปรแกรมอื่น ๆ นั่นอาจเป็นข้อบกพร่องหวังว่า
David Tonhofer

2
หากคุณให้รหัสแก่shellcheckคุณจะได้รับคำเตือน: SC2030 : "การแก้ไข var เป็นแบบโลคัล (ไปยัง subshell ที่เกิดจากไปป์ไลน์)"และSC2031 : "var ถูกแก้ไขใน subshell การเปลี่ยนแปลงนั้นอาจสูญหายไป" . ยอดเยี่ยม
David Tonhofer

ทำไมต้องใช้findและmapfileที่นี่เลยไม่ใช่แค่myarr=(mysqldump*)? สิ่งนี้จะทำงานกับชื่อไฟล์ที่มีช่องว่างและการขึ้นบรรทัดใหม่
BlackJack

1
เพิ่งสังเกตเห็นว่าจะต้องเปิดnullglobตัวเลือกใน ( shopt -s nullglob) เพื่อที่myarr=(mysqldump*)จะไม่จบลงด้วยอาร์เรย์('mysqldump*')ในกรณีที่ไม่มีไฟล์ที่ตรงกัน
David Tonhofer

คำตอบ:


23

จากman 1 bash:

แต่ละคำสั่งในไปป์ไลน์จะถูกดำเนินการเป็นกระบวนการแยกต่างหาก (เช่นใน subshell)

subshells ดังกล่าวสืบทอดตัวแปรจากเชลล์หลัก แต่มีความเป็นอิสระ ซึ่งหมายความว่าในคำสั่งเดิมของคุณดำเนินการได้ด้วยตัวเองmapfile myarrจากนั้นecho(อยู่นอกท่อ) พิมพ์ว่างเปล่าmyarr(ซึ่งเป็นเปลือกหลักmyarr)

คำสั่งนี้ทำงานแตกต่างกัน:

find . -maxdepth 1 -name "mysqldump*" | { mapfile -t myarr; echo "${myarr[@]}"; }

ในกรณีนี้mapfileและechoทำงานในแบบเดียวกันmyarr(ซึ่งไม่ใช่เปลือกหลักmyarr)

ในการเปลี่ยนเชลล์หลักmyarrคุณต้องรันmapfileในเชลล์หลักทั้งหมด ตัวอย่าง:

myarr=()
mapfile -t myarr < <(find . -maxdepth 1 -name "mysqldump*")
echo "${myarr[@]}"

เพิ่มลิงก์ไปที่ "process substituion" ตามที่กำหนดในการตอบกลับของ Attie ในกรณีที่ผู้เข้าชมมีช่วงเวลา TL; DR
เดวิด TONHOFER

10

Bash จะรันคำสั่งของไพพ์ไลน์ในสภาพแวดล้อม subshell ดังนั้นการกำหนดตัวแปร ฯลฯ ที่เกิดขึ้นภายในเชลล์จะไม่ปรากฏให้เห็นในส่วนที่เหลือของเชลล์

Dash (Debian's /bin/sh) และ busybox shนั้นคล้ายกันในขณะที่ zsh และ ksh รันส่วนสุดท้ายในเชลล์หลัก ใน Bash คุณสามารถใช้shopt -s lastpipeเพื่อทำสิ่งเดียวกัน แต่ใช้ได้เฉพาะเมื่อการควบคุมงานถูกปิดใช้งานดังนั้นจึงไม่ได้อยู่ในเชลล์แบบโต้ตอบโดยค่าเริ่มต้น

ดังนั้น:

$ bash -c 'x=a; echo b | read x; echo $x'
a
$ bash -c 'shopt -s lastpipe; x=a; echo b | read x; echo $x'
b

( readและmapfileมีปัญหาเดียวกัน)

อีกทางเลือกหนึ่ง (และตามที่ Attie) กล่าวถึงให้ใช้การทดแทนกระบวนการซึ่งทำงานเหมือนไพพ์ทั่วไปและได้รับการสนับสนุนใน Bash, ksh และ zsh

$ bash -c 'x=a; read x < <(echo b); echo $x'
b

POSIX ปล่อยให้มันไม่ระบุหากส่วนต่าง ๆ ของไปป์ไลน์ทำงานใน subshells หรือไม่ดังนั้นจึงไม่สามารถพูดได้จริงๆว่าเชลล์ใด ๆ จะ "ผิดปกติ" ในเรื่องนี้


1
หากคุณปิดใช้งานการควบคุมงานของ bash คุณสามารถใช้ Lastpipe ในเชลล์แบบอินเทอร์แอคทีฟได้เช่นกันset +m; shopt -s lastpipe; x=a; echo b | read x; echo $x; set -m
Cyrus

@Cyrus ใช่แล้วฉันจะลืมรายละเอียดขอบคุณ
ilkkachu

8

เมื่อ Kamil ได้ชี้ให้เห็นองค์ประกอบแต่ละอย่างในท่อเป็นกระบวนการแยกต่างหาก

คุณสามารถใช้ต่อไปทดแทนกระบวนการที่จะได้รับfindการเรียกใช้ในกระบวนการที่แตกต่างกันกับmapfileการภาวนาที่เหลืออยู่ในล่ามปัจจุบันของคุณช่วยให้เข้าถึงmyarrหลังจากนั้น:

myarr=()
mapfile -t myarr < <( find . -maxdepth 1  -name "mysqldump*" )
echo "${myarr[@]}"

b < <( a )จะทำหน้าที่คล้ายกับa | bในแง่ของวิธีการวางสายเชื่อมต่อ - ความแตกต่างbคือเรียกใช้งาน " ที่นี่ "

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.