Tidak bisa memasang "mapfile" di bash ... tapi mengapa?

13

Saya hanya ingin memasukkan semua file dalam direktori tertentu ke bash array (dengan asumsi tidak ada file yang memiliki baris baru dalam namanya):

Begitu:

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

Hasil kosong!

Jika saya melakukan cara bundaran menggunakan file, sementara atau sebaliknya:

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

Hasil!

Tetapi mengapa tidak mapfilemembaca dengan benar dari sebuah pipa?

David Tonhofer
sumber
Jawaban luar biasa di sekeliling, terima kasih semuanya. Menarik bagaimana strategi pelaksanaan pipa (setiap bagian berjalan dalam proses spearate) bocor "ke atas" dan memodifikasi makna kode yang jelas, pada dasarnya diam-diam menempatkan "lokal" di depan setiap variabel yang muncul di pipa. Mudah-mudahan dalam bahasa yang bukan sekadar lem gila untuk program lain, itu akan menjadi bug.
David Tonhofer
2
Jika Anda memberikan kode ke shellcheck , Anda mendapatkan peringatan: SC2030 : "Modifikasi var adalah lokal (untuk subkulit yang disebabkan oleh pipa)" dan SC2031 : "var telah diubah dalam subkulit. Perubahan itu mungkin hilang." . Luar biasa.
David Tonhofer
Mengapa menggunakan finddan mapfiledi sini sama sekali dan bukan hanya sekadar myarr=(mysqldump*)? Ini bahkan akan bekerja dengan nama file dengan spasi dan baris baru.
BlackJack
1
Hanya perhatikan bahwa kita harus mengaktifkan nullglobopsi ( shopt -s nullglob) untuk myarr=(mysqldump*)agar tidak berakhir dengan array ('mysqldump*')jika tidak ada file yang cocok.
David Tonhofer

Jawaban:

25

Dari man 1 bash:

Setiap perintah dalam sebuah pipa dieksekusi sebagai proses terpisah (yaitu, dalam subkulit).

Subkulit tersebut mewarisi variabel dari shell utama tetapi mereka independen. Ini berarti mapfiledalam perintah asli Anda beroperasi sendiri myarr. Kemudian echo(berada di luar pipa) mencetak kosong myarr(yang merupakan cangkang utama myarr).

Perintah ini bekerja secara berbeda:

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

Dalam hal ini mapfiledan echoberoperasi pada yang sama myarr(yang bukan shell utama myarr).

Untuk mengubah shell utama, myarrAnda harus menjalankan mapfileshell utama dengan tepat. Contoh:

myarr=()
mapfile -t myarr < <(find . -maxdepth 1 -name "mysqldump*")
echo "${myarr[@]}"
Kamil Maciorowski
sumber
Menambahkan tautan ke "proses subtitusi" seperti yang diberikan dalam respons Attie, jika pengunjung memiliki momen TL; DR.
David Tonhofer
11

Bash menjalankan perintah-perintah pipa di lingkungan subkulit, sehingga setiap penugasan variabel, dll. Yang terjadi di dalamnya tidak terlihat oleh sisa shell.

Dash (Debian /bin/sh) dan busybox shmirip, sedangkan zsh dan ksh menjalankan bagian terakhir di shell utama. Di Bash, Anda dapat menggunakan shopt -s lastpipeuntuk melakukan hal yang sama, tetapi ini hanya berfungsi ketika kontrol pekerjaan dinonaktifkan, jadi tidak dalam shell interaktif secara default.

Begitu:

$ 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

( readdan mapfilememiliki masalah yang sama.)

Atau (dan seperti yang disebutkan oleh Attie), gunakan substitusi proses , yang bekerja seperti pipa umum, dan didukung di Bash, ksh dan zsh.

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

POSIX membiarkannya tidak ditentukan jika bagian-bagian dari pipa berjalan dalam subkulit atau tidak, sehingga tidak dapat benar-benar dikatakan bahwa setiap kerang akan "salah" dalam hal ini.

ilkkachu
sumber
2
Jika Anda menonaktifkan kontrol pekerjaan bash, Anda dapat menggunakan lastpipe dalam shell interaktif juga:set +m; shopt -s lastpipe; x=a; echo b | read x; echo $x; set -m
Cyrus
@Cyrus, ah benar, saya lupa detailnya, terima kasih
ilkkachu
9

Seperti yang Kamil tunjukkan, setiap elemen dalam pipa merupakan proses terpisah.

Anda dapat menggunakan berikut proses substitusi untuk mendapatkan finduntuk berjalan dalam proses yang berbeda, dengan mapfiledoa yang tersisa di interpreter Anda saat ini, memungkinkan akses ke myarrsetelahnya:

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

b < <( a )akan bertindak serupa a | bdalam hal bagaimana pipa disambungkan - perbedaannya adalah yang bdieksekusi "di sini ".

Attie
sumber