Mengatur variabel lingkungan sebelum perintah di Bash tidak berfungsi untuk perintah kedua dalam pipa

351

Dalam shell yang diberikan, biasanya saya akan menetapkan variabel atau variabel dan kemudian menjalankan perintah. Baru-baru ini saya belajar tentang konsep menambahkan definisi variabel ke sebuah perintah:

FOO=bar somecommand someargs

Ini berfungsi ... agak. Itu tidak berfungsi ketika Anda mengubah variabel LC_ * (yang tampaknya memengaruhi perintah, tetapi tidak argumennya, misalnya, rentang char '[az]') atau ketika memipitkan output ke perintah lain dengan demikian:

FOO=bar somecommand someargs | somecommand2  # somecommand2 is unaware of FOO

Saya dapat menambahkan somecommand2 dengan "FOO = bar" juga, yang berfungsi, tetapi yang menambahkan duplikasi yang tidak diinginkan, dan itu tidak membantu dengan argumen yang ditafsirkan tergantung pada variabel (misalnya, '[az]').

Jadi, apa cara yang baik untuk melakukan ini pada satu baris?

Saya sedang memikirkan sesuatu berdasarkan urutan:

FOO=bar (somecommand someargs | somecommand2)  # Doesn't actually work

Saya mendapat banyak jawaban bagus! Tujuannya adalah untuk membuat ini satu-liner, lebih disukai tanpa menggunakan "ekspor". Metode menggunakan panggilan ke Bash adalah yang terbaik secara keseluruhan, meskipun versi kurung dengan "ekspor" di dalamnya sedikit lebih kompak. Metode menggunakan redirection bukan pipa juga menarik.

MartyMacGyver
sumber
1
(T=$(date) echo $T)akan bekerja
vp_arth
Dalam konteks skrip lintas platform (termasuk windows) atau proyek berbasis npm (js atau yang lain), Anda mungkin ingin melihat pada modul cross-env .
Frank Nocke
5
Saya berharap salah satu jawaban juga akan menjelaskan mengapa ini hanya berfungsi, yaitu mengapa tidak setara dengan mengekspor variabel sebelum panggilan.
Brecht Machiels
4
Mengapa dijelaskan di sini: stackoverflow.com/questions/13998075/…
Brecht Machiels

Jawaban:

317
FOO=bar bash -c 'somecommand someargs | somecommand2'
Dijeda sampai pemberitahuan lebih lanjut.
sumber
2
Ini memenuhi kriteria saya (satu-liner tanpa perlu "ekspor") ... Saya menganggapnya tidak ada cara untuk melakukan ini tanpa memanggil "bash -c" (mis., Penggunaan kreatif tanda kurung)?
MartyMacGyver
1
@ MartyMacGyver: Tidak ada yang bisa saya pikirkan. Ini juga tidak akan bekerja dengan kawat gigi keriting.
Dijeda sampai pemberitahuan lebih lanjut.
7
Perhatikan bahwa jika Anda perlu menjalankan somecommandsudo Anda, Anda harus memberikan sudo -Eflag untuk meneruskan variabel. Karena variabel dapat memperkenalkan kerentanan. stackoverflow.com/a/8633575/1695680
ThorSummoner
11
Perhatikan bahwa jika perintah Anda sudah memiliki dua tingkat kutipan maka metode ini menjadi sangat tidak memuaskan karena kutipan neraka. Dalam situasi itu mengekspor dalam subkulit jauh lebih baik.
Pushpendre
Ganjil: pada OSX, FOO_X=foox bash -c 'echo $FOO_X'berfungsi seperti yang diharapkan tetapi dengan nama var tertentu gagal: DYLD_X=foox bash -c 'echo $DYLD_X'gema kosong. keduanya bekerja menggunakan evalbukanbash -c
mwag
209

Bagaimana dengan mengekspor variabel, tetapi hanya di dalam subkulit ?:

(export FOO=bar && somecommand someargs | somecommand2)

Keith ada benarnya, untuk mengeksekusi perintah tanpa syarat, lakukan ini:

(export FOO=bar; somecommand someargs | somecommand2)
0xC0000022L
sumber
15
Saya akan menggunakan ;daripada &&; tidak mungkin export FOO=barakan gagal.
Keith Thompson
1
@ MartyMacGyver: &&mengeksekusi perintah kiri, kemudian mengeksekusi perintah kanan hanya jika perintah kiri berhasil. ;mengeksekusi kedua perintah tanpa syarat. Windows Batch ( cmd.exe) setara ;adalah &.
Keith Thompson
2
Di zsh saya sepertinya tidak membutuhkan ekspor untuk versi ini: (FOO=XXX ; echo FOO=$FOO) ; echo FOO=$FOOhasil FOO=XXX\nFOO=\n.
rampion
3
@PopePoopinpants: mengapa tidak menggunakan source(alias .) dalam kasus itu? Juga, backticks tidak boleh digunakan lagi hari ini dan ini adalah salah satu alasan mengapa, menggunakan $(command)waaaaay lebih aman.
0xC0000022L
3
Sangat sederhana, namun begitu elegan. Dan saya suka jawaban Anda lebih baik daripada jawaban yang diterima, karena akan memulai sub shell sama dengan saya saat ini (yang mungkin tidak bashtetapi bisa menjadi sesuatu yang lain, misalnya dash) dan saya tidak mengalami kesulitan jika saya harus menggunakan tanda kutip dalam perintah args ( someargs).
Mecki
45

Anda juga dapat menggunakan eval:

FOO=bar eval 'somecommand someargs | somecommand2'

Karena jawaban dengan evalini sepertinya tidak menyenangkan semua orang, izinkan saya mengklarifikasi sesuatu: ketika digunakan sebagai tulisan, dengan tanda kutip tunggal , itu sangat aman. Itu bagus karena tidak akan meluncurkan proses eksternal (seperti jawaban yang diterima) juga tidak akan menjalankan perintah dalam subkulit tambahan (seperti jawaban lainnya).

Ketika kita mendapatkan beberapa pandangan reguler, mungkin baik untuk memberikan alternatif evalyang akan menyenangkan semua orang, dan memiliki semua manfaat (dan mungkin bahkan lebih!) Dari eval"trik" cepat ini . Cukup gunakan fungsi! Tetapkan fungsi dengan semua perintah Anda:

mypipe() {
    somecommand someargs | somecommand2
}

dan jalankan dengan variabel lingkungan Anda seperti ini:

FOO=bar mypipe
gniourf_gniourf
sumber
7
@Alfe: Apakah Anda juga menurunkan jawaban yang diterima? karena menunjukkan "masalah" yang sama dengan eval.
gniourf_gniourf
10
@Alfe: sayangnya saya tidak setuju dengan kritik Anda. Perintah ini sangat aman. Anda benar-benar terdengar seperti orang yang pernah membaca evaladalah kejahatan tanpa memahami apa yang jahat tentang eval. Dan mungkin Anda tidak benar-benar memahami jawaban ini (dan benar-benar tidak ada yang salah dengan itu). Pada tingkat yang sama: apakah Anda akan mengatakan itu lsburuk karena for file in $(ls)itu, buruk? (dan ya, Anda juga tidak memilih jawaban yang diterima, dan Anda juga tidak meninggalkan komentar). SO terkadang merupakan tempat yang aneh dan absurd.
gniourf_gniourf
7
@Alfe: ketika saya mengatakan Anda benar-benar terdengar seperti orang yang pernah membaca evaladalah jahat tanpa memahami apa yang jahat tentang eval, saya mengacu pada kalimat Anda: Jawaban ini tidak memiliki semua peringatan dan penjelasan yang diperlukan ketika berbicara tentang eval. evaltidak buruk atau berbahaya; tidak lebih dari bash -c.
gniourf_gniourf
1
Selain suara, komentar yang diberikan @Alfe entah bagaimana menyiratkan bahwa jawaban yang diterima entah bagaimana lebih aman. Apa yang akan lebih membantu bagi Anda untuk menggambarkan apa yang Anda yakini tidak aman tentang penggunaan eval. Dalam jawaban yang disediakan args telah dikutip tunggal melindungi dari ekspansi variabel, jadi saya tidak melihat masalah dengan jawabannya.
Brett Ryan
Saya menghapus komentar saya untuk memusatkan perhatian saya dalam satu komentar baru: evaladalah masalah keamanan secara umum (seperti bash -ctapi kurang jelas), sehingga bahaya harus disebutkan dalam jawaban yang mengusulkan penggunaannya. Pengguna yang ceroboh dapat mengambil jawabannya ( FOO=bar eval …) dan menerapkannya pada situasi mereka sehingga menimbulkan masalah. Tetapi jelas lebih penting bagi penjawab untuk mencari tahu apakah saya menurunkan jawaban dan / atau jawaban lain daripada memperbaiki apa pun. Seperti yang saya tulis sebelumnya, keadilan seharusnya tidak menjadi perhatian utama; menjadi tidak lebih buruk daripada jawaban yang diberikan lainnya juga terlepas.
Alfe
12

Gunakan env.

Sebagai contoh env FOO=BAR command,. Perhatikan bahwa variabel lingkungan akan dipulihkan / tidak berubah lagi ketika commandselesai dijalankan.

Berhati-hatilah dengan substitusi shell yang terjadi, yaitu jika Anda ingin referensi $FOOsecara eksplisit pada baris perintah yang sama, Anda mungkin perlu menghindarinya sehingga interpreter shell Anda tidak melakukan substitusi sebelum dijalankan env.

$ export FOO=BAR
$ env FOO=FUBAR bash -c 'echo $FOO'
FUBAR
$ echo $FOO
BAR
benjimin
sumber
-5

Gunakan skrip shell:

#!/bin/bash
# myscript
FOO=bar
somecommand someargs | somecommand2

> ./myscript
Spencer Rathbun
sumber
13
Anda masih membutuhkan export; jika tidak $FOOakan menjadi variabel shell, bukan variabel lingkungan, dan karenanya tidak terlihat somecommandatau somecommand2.
Keith Thompson
Ini akan berhasil tetapi itu mengalahkan tujuan memiliki perintah satu baris (saya mencoba mempelajari cara-cara yang lebih kreatif untuk menghindari multi-liner dan / atau skrip untuk kasus yang relatif sederhana). Dan apa yang @Keith katakan, meskipun setidaknya ekspor akan tetap mencakup skrip.
MartyMacGyver