Saya ingin dapat menangkap output yang tepat dari substitusi perintah, termasuk karakter baris baru yang tertinggal .
Saya menyadari bahwa mereka dilucuti secara default, jadi beberapa manipulasi mungkin diperlukan untuk menyimpannya, dan saya ingin menyimpan kode keluar yang asli .
Misalnya, diberi perintah dengan sejumlah variabel baris baru dan kode keluar:
f(){ for i in $(seq "$((RANDOM % 3))"); do echo; done; return $((RANDOM % 256));}
export -f f
Saya ingin menjalankan sesuatu seperti:
exact_output f
Dan hasilnya adalah:
Output: $'\n\n'
Exit: 5
Saya tertarik pada keduanya bash
dan POSIX sh
.
$IFS
, sehingga tidak akan ditangkap sebagai argumen.IFS
(coba( IFS=:; subst=$(printf 'x\n\n\n'); printf '%s' "$subst" )
. Hanya baris baru dilepaskan.\t
Dan `` tidak, danIFS
tidak mempengaruhinya.tcsh
Jawaban:
Kerang POSIX
Biasa ( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Trik ) untuk mendapatkan stdout lengkap dari sebuah perintah adalah dengan melakukan:
Idenya adalah menambah dan ekstra
.\n
. Substitusi perintah hanya akan menghapus itu\n
. Dan Anda menghapusnya.
dengan${output%.}
.Perhatikan bahwa dalam cangkang selain
zsh
, itu masih tidak akan berfungsi jika output memiliki byte NUL. Denganyash
, itu tidak akan berfungsi jika outputnya bukan teks.Perhatikan juga bahwa di beberapa lokal, penting karakter apa yang Anda gunakan untuk memasukkan di akhir.
.
umumnya harus baik-baik saja, tetapi yang lain mungkin tidak. Misalnyax
(seperti yang digunakan dalam beberapa jawaban lain) atau@
tidak akan berfungsi di lokal menggunakan rangkaian karakter BIG5, GB18030 atau BIG5HKSCS. Dalam rangkaian karakter tersebut, penyandian sejumlah karakter berakhir dengan byte yang sama dengan penyandianx
atau@
(0x78, 0x40)Misalnya,
ū
dalam BIG5HKSCS adalah 0x88 0x78 (danx
0x78 seperti di ASCII, semua rangkaian karakter pada sistem harus memiliki penyandian yang sama untuk semua karakter set karakter portabel yang mencakup huruf bahasa Inggris,@
dan.
). Jadi jikacmd
ituprintf '\x88'
dan kita masukkanx
setelah itu,${output%x}
akan gagal untuk menghapusnyax
sebagai$output
sebenarnya mengandungū
.Penggunaan
.
sebaliknya dapat menyebabkan masalah yang sama secara teori jika ada karakter yang pengkodeannya berakhir dengan pengkodean yang sama dengan.
, tetapi karena telah memeriksa beberapa waktu yang lalu, saya dapat mengatakan bahwa tidak ada rangkaian karakter yang mungkin tersedia untuk digunakan di lokal di sistem Debian, FreeBSD, atau Solaris memiliki karakter seperti itu yang cukup baik untuk saya (dan mengapa saya memilih.
yang juga merupakan simbol untuk menandai akhir kalimat dalam bahasa Inggris sehingga tampaknya sesuai).Pendekatan yang lebih tepat seperti yang dibahas oleh @Arrow adalah mengubah lokal menjadi C hanya untuk pengupasan karakter terakhir (
${output%.}
) yang akan memastikan hanya satu byte dilucuti, tetapi itu akan menyulitkan kode secara signifikan dan berpotensi menimbulkan masalah kompatibilitas dari itu sendiri.alternatif bash / zsh
Dengan
bash
danzsh
, dengan asumsi output tidak memiliki NUL, Anda juga dapat melakukan:Untuk mendapatkan status keluar dari
cmd
, Anda dapat melakukanwait "$!"; ret=$?
dibash
tetapi tidak dalamzsh
.rc / es / akanaga
Untuk kelengkapan, perhatikan bahwa
rc
/es
/akanga
ada operator untuk itu. Di dalamnya, substitusi perintah, dinyatakan sebagai`cmd
(atau`{cmd}
untuk perintah yang lebih kompleks) mengembalikan daftar (dengan memisahkan$ifs
, spasi-tab-baris baru secara default). Dalam cangkang tersebut (berbeda dengan cangkang mirip Bourne), pengupasan baris baru hanya dilakukan sebagai bagian dari$ifs
pemisahan itu. Jadi Anda bisa mengosongkan$ifs
atau menggunakan``(seps){cmd}
formulir tempat Anda menentukan pemisah:atau:
Bagaimanapun, status keluar dari perintah hilang. Anda harus menanamkannya di output dan mengekstraknya setelah itu yang akan menjadi jelek.
ikan
Dalam ikan, substitusi perintah adalah dengan
(cmd)
dan tidak melibatkan subkulit.Menciptakan
$var
array dengan semua baris dalam outputcmd
if$IFS
tidak kosong, atau dengan outputcmd
dilucuti hingga satu (sebagai lawan dari semua di kebanyakan shell) karakter baris baru jika$IFS
kosong.Jadi masih ada masalah dalam hal itu
(printf 'a\nb')
dan(printf 'a\nb\n')
berkembang ke hal yang sama bahkan dengan yang kosong$IFS
.Untuk mengatasinya, yang terbaik yang bisa saya pikirkan adalah:
Alternatifnya adalah dengan melakukan:
Shell Bourne
Shell Bourne tidak mendukung
$(...)
bentuk atau${var%pattern}
operator, sehingga sangat sulit untuk mencapai di sana. Salah satu pendekatan adalah menggunakan eval dan mengutip:Di sini, kami menghasilkan
untuk diteruskan ke
eval
. Adapun pendekatan POSIX, jika'
salah satu karakter yang pengkodeannya dapat ditemukan di akhir karakter lain, kita akan memiliki masalah (yang jauh lebih buruk karena akan menjadi kerentanan injeksi perintah), tapi untungnya, seperti.
, itu bukan salah satu dari itu, dan teknik mengutip umumnya yang digunakan oleh apa pun yang mengutip kode shell (catatan yang\
memiliki masalah, jadi tidak boleh digunakan (juga tidak termasuk"..."
di dalamnya Anda perlu menggunakan garis miring terbalik untuk beberapa karakter) Di sini, kami hanya menggunakannya setelah'
yang OK).tcsh
Lihat tcsh mempertahankan baris baru dalam substitusi perintah `...`
(tidak menjaga status keluar, yang dapat Anda atasi dengan menyimpannya dalam file sementara (
echo $status > $tempfile:q
setelah perintah))sumber
zsh
bisa menyimpanNUL
dalam variabel, mengapa tidakIFS= read -rd '' output < <(cmd)
berhasil? Itu harus dapat menyimpan panjang string ... apakah itu dikodekan''
sebagai string 1-byte\0
daripada string 0-byte?read -d ''
diperlakukan sebagairead -d $'\0'
(bash
meskipun juga$'\0'
sama di''
mana - mana).x
jika itu yang ditambahkan. Silakan lihat jawaban saya yang diedit.var=value command eval
triknya sudah dibahas di sini ( juga ) dan di milis austin-grup sebelumnya. Anda akan menemukan itu tidak portabel (dan sangat jelas ketika Anda mencoba hal-hal sepertia=1 command eval 'unset a; a=2'
atau lebih buruk bahwa itu tidak dimaksudkan untuk digunakan seperti itu). Sama untuksavedVAR=$VAR;...;VAR=$savedVAR
yang tidak melakukan apa yang Anda inginkan ketika$VAR
awalnya tidak disetel. Jika itu hanya untuk mengatasi masalah teoretis saja (bug yang tidak dapat dipukul dalam praktiknya), IMO, itu tidak layak untuk diganggu. Tetap saja, saya akan mendukung Anda untuk mencoba.LANG=C
untuk menghapus byte dari sebuah string? Anda mengemukakan kekhawatiran di sekitar titik nyata, semua mudah dipecahkan. (1) tidak ada yang tidak disetel yang digunakan (2) Uji variabel sebelum mengubahnya. @ StéphaneChazelasUntuk pertanyaan baru, skrip ini berfungsi:
Pada eksekusi:
Deskripsi yang lebih panjang
Kebijaksanaan biasa untuk kerang POSIX untuk menangani penghapusan
\n
adalah:Itu diperlukan karena baris baru terakhir ( S ) dihapus oleh perintah ekspansi per spesifikasi POSIX :
Tentang trailing
x
.Telah dikatakan dalam pertanyaan ini bahwa sebuah
x
dapat dikacaukan dengan byte trailing dari beberapa karakter dalam beberapa pengkodean. Tetapi bagaimana kita akan menebak karakter apa atau yang lebih baik dalam suatu bahasa dalam beberapa penyandian yang mungkin, itu adalah proposisi yang sulit, untuk sedikitnya.Namun; Itu tidak benar .
Satu-satunya aturan yang perlu kita ikuti adalah menambahkan dengan tepat apa yang kita hapus.
Seharusnya mudah dipahami bahwa jika kita menambahkan sesuatu ke string yang sudah ada (atau urutan byte) dan kemudian kita menghapus sesuatu yang persis sama, string asli (atau urutan byte) harus sama.
Di mana kita salah? Ketika kita mencampur karakter dan byte .
Jika kita menambahkan byte, kita harus menghapus byte, jika kita menambahkan karakter, kita harus menghapus karakter yang sama persis .
Opsi kedua, menambahkan karakter (dan kemudian menghapus karakter yang sama persis) dapat menjadi berbelit-belit dan kompleks, dan, ya, halaman kode dan penyandian mungkin menghalangi.
Namun, opsi pertama sangat mungkin, dan, setelah menjelaskannya, itu akan menjadi sederhana.
Mari kita tambahkan byte, byte ASCII (<127), dan untuk menjaga hal-hal sesederhana mungkin, katakanlah karakter ASCII dalam kisaran az. Atau seperti yang seharusnya kita katakan, byte dalam kisaran hex
0x61
-0x7a
. Mari kita pilih salah satunya, mungkin x (benar-benar nilai byte0x78
). Kita dapat menambahkan byte tersebut dengan menggabungkan x ke sebuah string (mari kita asumsikan sebuahé
):Jika kita melihat string sebagai urutan byte, kita melihat:
Urutan string yang berakhiran x.
Jika kita menghapus x itu (nilai byte
0x78
), kita mendapatkan:Ini bekerja tanpa masalah.
Contoh yang sedikit lebih sulit.
Katakanlah bahwa string yang kita minati diakhiri dengan byte
0xc3
:Dan mari kita tambahkan satu byte nilai
0xa9
String telah menjadi ini sekarang:
Tepat seperti yang saya inginkan, dua byte terakhir adalah satu karakter di utf8 (sehingga siapa pun dapat mereproduksi hasil ini di konsol utf8 mereka).
Jika kita menghapus karakter, string asli akan berubah. Tapi bukan itu yang kami tambahkan, kami menambahkan nilai byte, yang kebetulan ditulis sebagai x, tetapi byte tetap.
Yang perlu kita hindari salah mengartikan byte sebagai karakter. Yang kami butuhkan adalah tindakan yang menghapus byte yang kami gunakan
0xa9
. Bahkan, abu, bash, lksh, dan mksh semuanya tampaknya melakukan hal itu:Tapi bukan ksh atau zsh.
Namun, itu sangat mudah dipecahkan, mari beri tahu semua shell untuk melakukan penghapusan byte:
itu saja, semua kerang yang diuji bekerja (kecuali yash) (untuk bagian terakhir dari string):
Sederhananya, beri tahu shell untuk menghapus karakter LC_ALL = C, yang persis satu byte untuk semua nilai byte dari
0x00
ke0xff
.Solusi untuk komentar:
Sebagai contoh yang dibahas dalam komentar, satu solusi yang mungkin (yang gagal dalam zsh) adalah:
Itu akan menghapus masalah pengkodean.
sumber
zsh
ditambahkanprintf -v
untuk kompatibilitas denganbash
pada Desember 2015${var%?}
selalu menghapus satu byte lebih benar secara teori, tetapi: 1-LC_ALL
danLC_CTYPE
menimpa$LANG
, jadi Anda harus mengaturLC_ALL=C
2- Anda tidak dapat melakukanvar=${var%?}
dalam subkulit seperti perubahan akan hilang, jadi Anda harus menyimpan dan mengembalikan nilai dan statusLC_ALL
(atau menggunakanlocal
fitur lingkup non-POSIX ) 3- mengubah lokal di tengah-tengah skrip tidak sepenuhnya didukung di beberapa shell seperti yash. Di sisi lain, dalam praktiknya.
tidak pernah menjadi masalah di rangkaian karakter kehidupan nyata, jadi menggunakannya tidak akan bergaul dengan LC_ALL.Anda dapat menampilkan karakter setelah output normal dan kemudian menghapusnya:
Ini adalah solusi yang sesuai dengan POSIX.
sumber