Variabel lingkungan tidak disetel ketika fungsi saya dipanggil dalam pipeline


10

Saya memiliki fungsi rekursif berikut untuk mengatur variabel lingkungan:

function par_set {
  PAR=$1
  VAL=$2
  if [ "" != "$1" ]
  then
    export ${PAR}=${VAL}
    echo ${PAR}=${VAL}
    shift
    shift
    par_set $*
  fi
}

Jika saya memanggilnya dengan sendirinya, keduanya mengatur variabel dan menggema ke stdout:

$ par_set FN WORKS
FN=WORKS
$ echo "FN = "$FN
FN = WORKS

Mengarahkan stdout ke file juga berfungsi:

$ par_set REDIR WORKS > out
cat out
REDIR=WORKS
$ echo "REDIR = "$REDIR
REDIR = WORKS

Tapi, jika saya mem-pipe stdout ke perintah lain, variabel tidak disetel:

$ par_set PIPE FAILS |sed -e's/FAILS/BARFS/'
PIPE=BARFS
$ echo "PIPE = "$PIPE
PIPE =

Mengapa pipa mencegah fungsi mengekspor variabel? Apakah ada cara untuk memperbaikinya tanpa menggunakan temp file atau pipa bernama?

Terpecahkan:

Kode kerja terima kasih kepada Gilles:

par_set $(echo $*|tr '=' ' ') > >(sed -e's/^/  /' >> ${LOG})

Ini memungkinkan skrip dipanggil demikian:

$ . ./script.sh PROCESS_SUB ROCKS PIPELINES=NOGOOD
$ echo $PROCESS_SUB
ROCKS
$ echo $PIPELINES
NOGOOD
$ cat log
7:20140606155622162731431:script.sh:29581:Parse Command Line parameters.  Params must be in matched pairs separated by one or more '=' or ' '.
  PROCESS_SUB=ROCKS
  PIPELINES=NOGOOD

Proyek dihosting di bitbucket https://bitbucket.org/adalby/monitor-bash jika tertarik dengan kode lengkap.

Jawaban:


8

Setiap bagian dari pipa (yaitu masing-masing sisi pipa) berjalan dalam proses terpisah (disebut subkulit, ketika sebuah shell memotong sebuah subproses untuk menjalankan bagian dari skrip). Dalam par_set PIPE FAILS |sed -e's/FAILS/BARFS/', PIPEvariabel diatur dalam subproses yang mengeksekusi sisi kiri pipa. Perubahan ini tidak tercermin dalam proses induk (variabel lingkungan tidak mentransfer antar proses, mereka hanya diwariskan oleh subproses.

Sisi kiri pipa selalu berjalan dalam subkulit. Beberapa shell (ATT ksh, zsh) menjalankan sisi kanan pada shell induk; sebagian besar juga menjalankan sisi kanan dalam subkulit.

Jika Anda ingin mengarahkan ulang output dari bagian skrip dan menjalankan bagian itu di shell induk, di ksh / bash / zsh, Anda dapat menggunakan subtitusi proses .

par_set PROCESS SUBSTITUTION > >(sed s/ION/ED/)

Dengan shell POSIX, Anda dapat mengarahkan output ke pipa bernama.

mkfifo f
<f grep NAMED= &
par_set NAMED PIPE >f

Oh, dan Anda kehilangan tanda kutip di sekitar penggantian variabel , kode Anda rusak pada hal-hal seperti par_set name 'value with spaces' star '*'.

export "${PAR}=${VAL}"

par_set "$@"

Proses substitusi untuk kemenangan! Saya tahu saya bisa menggunakan pipa bernama atau file temp, tetapi itu jelek, memiliki konkurensi yang buruk, dan meninggalkan kekacauan jika skrip mati (trap membantu dengan yang terakhir). Ruang angkasa disengaja. Secara konvensi, variabel yang dikirimkan pada baris perintah ada dalam pasangan nama / nilai dan dipisahkan oleh '=' dan / atau ''.
Andrew

3

Ini tidak berfungsi karena setiap sisi pipa berjalan dalam subkulit masuk bash, dan variabel yang ditetapkan dalam subkulit lokal ke subkulit itu.

Memperbarui:

Sepertinya mudah untuk meneruskan variabel dari induk ke shell anak, tetapi sangat sulit untuk melakukannya dengan cara lain. Beberapa solusi dinamai pipa, file temp, menulis ke stdout dan membaca di orangtua, dll.

Beberapa referensi:

http://mywiki.wooledge.org/BashFAQ/024
https://stackoverflow.com/q/15541321/3565972
https://stackoverflow.com/a/15383353/3565972
http://forums.opensuse.org/showthread .php / 458979-How-export-variable-in-subshell-back-out-to-parent


Saya pikir itu suatu kemungkinan, tetapi fungsi harus dijalankan di shell saat ini. Saya menguji dengan menambahkan "echo $$" ke fungsi. par_set MASIH GAGAL | sed -e "s / ^ / sedpid = $$, fnpid = /" output sedpid = 15957, fnpid = 15957.
Andrew

@Andrew Lihat stackoverflow.com/a/20726041/3565972 ini . Rupanya, $$sama untuk cangkang orangtua dan anak. Anda dapat menggunakan $BASHPIDuntuk mendapatkan pid subshell. Ketika saya echo $$ $BASHPIDberada di dalam, par_setsaya mendapatkan nomor yang berbeda.
savanto

@Andrew Masih berusaha mencari solusi, tetapi gagal! =)
savanto

@ Savanto-Terima kasih. Saya tidak tahu bahwa sekitar $$ vs $ BASHPID atau pipa memaksa subkulit.
Andrew

0

Anda menunjukkan subkulit - yang bisa diselesaikan dengan beberapa finagling di shell di luar pipa - tetapi bagian yang lebih sulit dari masalah berkaitan dengan konkurensi pipa .

Semua anggota proses dari pipa mulai sekaligus , dan masalahnya mungkin lebih mudah untuk dipahami jika Anda melihatnya seperti ini:

{ sleep 1 ; echo $((f=1+2)) >&2 ; } | echo $((f))
###OUTPUT
0
...
3

Proses pipa tidak bisa mewarisi nilai variabel karena sudah mati dan berjalan sebelum variabel ditetapkan.

Saya tidak bisa benar-benar mengerti apa fungsi Anda - apa tujuannya exportbelum? Atau bahkan adil var=val? Misalnya, inilah saluran yang hampir sama lagi:

pipeline() { 
    { sleep 1
      echo "f=$((f=f+1+2))" >&3
    } | echo $((f)) >&2
} 3>&1

f=4 pipeline

###OUTPUT

4
...
f=7

Dan dengan export:

export $(f=4 pipeline) ; pipeline

###OUTPUT:

4
7
...
f=10

Jadi benda Anda bisa bekerja seperti:

par_set $(echo PIPE FAILS | 
    sed 's/FAIL/WORK/;/./w /path/to/log')

Yang akan masuk ke sedoutput file dan mengirimkannya ke fungsi Anda sebagai shell-split "$@".

Atau, sebagai alternatif:

$ export $(echo PIPE FAILS | sed 's/ FAIL/=WORK/')
$ par_set $PIPE TWICE_REMOVED
$ echo "WORKS = "$WORKS
WORKS = TWICE_REMOVED

Jika saya akan menulis fungsi Anda, mungkin akan terlihat seperti ini:

_env_eval() { 
    while ${2+:} false ; do
       ${1:+export} ${1%%["${IFS}"]*}="${2}" || :
       shift 2
    done
}

Itu benar, tetapi tidak relevan: Andrew mencoba menggunakan variabel setelah pipa berakhir, bukan di sisi lain pipa.
Gilles 'SANGAT berhenti menjadi jahat'

@Gilles - yah dia bisa melakukan itu. |pipeline | sh
mikeserv

@Gilles - sebenarnya, Anda bahkan tidak perlu sh. Dia sudah menggunakan export.
mikeserv

Tujuan keseluruhan adalah skrip pemantauan sistem. Ingin dapat memanggil mereka secara interaktif, dari skrip lain, atau dari cron. Ingin dapat melewati parameter dengan mengatur env vars, meneruskan pada baris perintah, atau file konfigurasi. Ada fungsi untuk mengambil input dari satu atau lebih sumber dan mengatur lingkungan dengan benar. Namun, saya juga ingin dapat secara opsional log output (setelah berjalan melalui format sed), maka kebutuhan untuk pipa.
Andrew

1
@ mikeserv- Terima kasih atas semua komentar. Saya mengikuti saran Gilles untuk substitusi proses, tetapi harus berdebat untuk kode saya membuat saya menjadi programmer yang lebih baik.
Andrew
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.