Biến môi trường không được đặt khi hàm của tôi được gọi trong đường ống


10

Tôi có hàm đệ quy sau để đặt biến môi trường:

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

Nếu tôi tự gọi nó, cả hai đều đặt biến và lặp lại thành thiết bị xuất chuẩn:

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

Chuyển hướng stdout sang một tập tin cũng hoạt động:

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

Nhưng, nếu tôi chuyển stdout sang lệnh khác, biến không được đặt:

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

Tại sao đường ống ngăn chức năng xuất biến? Có cách nào để khắc phục điều này mà không cần dùng đến các tệp tạm thời hoặc các đường ống được đặt tên không?

Đã giải quyết:

Mã làm việc nhờ Gilles:

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

Điều này cho phép tập lệnh được gọi như vậy:

$ . ./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

Dự án được lưu trữ trên bitbucket https://bitbucket.org/adalby/monitor-bash nếu quan tâm đến mã đầy đủ.

Câu trả lời:


8

Mỗi phần của một đường ống dẫn (ví dụ mỗi bên của đường ống) chạy trong một quá trình riêng biệt (gọi là subshell, khi một vỏ Forks một tiến trình con để chạy phần của kịch bản). Trong par_set PIPE FAILS |sed -e's/FAILS/BARFS/', PIPEbiến được đặt trong quy trình con thực hiện phía bên trái của đường ống. Sự thay đổi này không được phản ánh trong quy trình cha (các biến môi trường không chuyển giữa các quy trình, chúng chỉ được kế thừa bởi các quy trình con.

Phía bên trái của một đường ống luôn chạy trong một khung con. Một số shell (ATT ksh, zsh) chạy phía bên phải trong shell cha; hầu hết cũng chạy phía bên tay phải trong một khung con.

Nếu bạn muốn cả hai chuyển hướng đầu ra của một phần của tập lệnh và chạy phần đó trong shell cha, trong ksh / bash / zsh, bạn có thể sử dụng thay thế quy trình .

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

Với bất kỳ vỏ POSIX nào, bạn có thể chuyển hướng đầu ra sang một ống có tên.

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

Ồ, và bạn đang thiếu dấu ngoặc kép xung quanh các thay thế khác nhau , mã của bạn phá vỡ những thứ như par_set name 'value with spaces' star '*'.

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

par_set "$@"

Quá trình thay thế cho chiến thắng! Tôi biết rằng tôi có thể sử dụng một tệp ống hoặc temp có tên, nhưng chúng là xấu, có tính đồng thời kém và để lại một mớ hỗn độn nếu tập lệnh chết (bẫy giúp với cái cuối cùng). Điều không gian là cố ý. Theo quy ước, các biến được truyền trên dòng lệnh nằm trong cặp tên / giá trị và được phân tách bằng '=' và / hoặc ''.
Andrew

3

Điều này không hoạt động bởi vì mỗi bên của đường ống chạy trong một lớp con bashvà các biến được đặt trong một lớp con là cục bộ của lớp con đó.

Cập nhật:

Có vẻ như thật dễ dàng để chuyển các biến từ cha mẹ sang vỏ con, nhưng thực sự rất khó để làm điều đó theo cách khác. Một số cách giải quyết được đặt tên là ống dẫn, tệp tạm thời, ghi vào thiết bị xuất chuẩn và đọc trong phần gốc, v.v.

Một số tài liệu tham khảo:

http://mywiki.wooledge.org/BashFAQ/024
/programming//q/155413213565972
/programming//a/1538335335365972
http://forums.opensuse.org/showthread .php / 458979-How-export-biến-in-subshell-back-out-to-Parent


Tôi nghĩ đó là một khả năng, nhưng hàm sẽ thực thi trong trình bao hiện tại. Tôi đã kiểm tra bằng cách thêm "echo $$" vào hàm. par_set VẪN FAILS | sed -e "s / ^ / sedpid = $$, fnpid = /" output sedpid = 15957, fnpid = 15957.
Andrew

@Andrew Xem stackoverflow.com/a/20726041/3565972 . Rõ ràng, $$là giống nhau cho cả vỏ mẹ và con. Bạn có thể sử dụng $BASHPIDđể có được pid subshell. Khi tôi echo $$ $BASHPIDtrong par_settôi nhận được các pids khác nhau.
savanto

@Andrew Vẫn cố gắng tìm cách giải quyết, nhưng thất bại! =)
savanto

@ Savanto-Cảm ơn. Tôi không biết rằng khoảng $$ so với $ BASHPID hoặc đường ống buộc các subshells.
Andrew

0

Bạn chỉ ra các lớp con - có thể được xử lý bằng một số phần tử trong vỏ ngoài đường ống - nhưng phần khó hơn của vấn đề phải làm với sự tương tranh của đường ống .

Tất cả các thành viên quá trình của đường ống bắt đầu cùng một lúc , và vì vậy vấn đề có thể dễ hiểu hơn nếu bạn nhìn vào nó như thế này:

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

Các quy trình đường ống không thể kế thừa các giá trị biến vì chúng đã tắt và chạy trước khi biến được đặt.

Tôi thực sự không thể hiểu ý nghĩa của chức năng của bạn là gì - nó phục vụ mục đích gì mà exportchưa có? Hay thậm chí chỉ var=val? Ví dụ, đây là gần như cùng một đường ống một lần nữa:

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

f=4 pipeline

###OUTPUT

4
...
f=7

Và với export:

export $(f=4 pipeline) ; pipeline

###OUTPUT:

4
7
...
f=10

Vì vậy, điều của bạn có thể làm việc như:

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

Mà sẽ đăng nhập vào sedđầu ra của một tệp và phân phối nó đến chức năng của bạn dưới dạng phân tách vỏ "$@".

Hay cách khác:

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

Tuy nhiên, nếu tôi định viết chức năng của bạn, nó có thể sẽ trông như thế này:

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

Điều đó đúng, nhưng không liên quan: Andrew đang cố gắng sử dụng các biến sau khi đường ống kết thúc, không phải ở phía bên kia của đường ống.
Gilles 'SO- ngừng trở nên xấu xa'

@Gilles - anh ấy có thể làm điều đó. |pipeline | sh
mikeerv

@Gilles - thực sự, bạn thậm chí không cần sh. Anh ấy đã sử dụng export.
mikeerv

Mục tiêu tổng thể là các kịch bản giám sát hệ thống. Muốn có thể gọi chúng một cách tương tác, từ các tập lệnh khác hoặc từ cron. Muốn có thể truyền tham số bằng cách đặt env vars, truyền dòng lệnh hoặc tệp cấu hình. Chức năng tồn tại để lấy đầu vào từ một hoặc nhiều nguồn và đặt môi trường chính xác. Tuy nhiên, tôi cũng muốn có thể tùy ý đăng nhập đầu ra (sau khi chạy qua sed để định dạng), do đó cần phải đặt ống.
Andrew

1
@ mikeerv- Cảm ơn tất cả các ý kiến. Tôi đã đi với đề xuất của Gilles về sự thay thế quá trình, nhưng việc tranh luận về mã của tôi làm cho tôi trở thành một lập trình viên tốt hơn.
Andrew
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.