Lưu đầu ra của lệnh sửa đổi môi trường thành một biến


8

Làm thế nào để lưu đầu ra của một lệnh sửa đổi môi trường thành một biến?

Tôi đang sử dụng bash shell.

Giả sử rằng tôi có:

function f () { a=3; b=4 ; echo "`date`: $a $b"; }

Và bây giờ, tôi có thể sử dụng các lệnh để chạy f:

$ a=0; b=0; f; echo $a; echo $b; echo $c
Sat Jun 28 21:27:08 CEST 2014: 3 4
3
4

nhưng tôi muốn lưu đầu ra của fbiến c, vì vậy tôi đã thử:

a=0; b=0; c=""; c=$(f); echo $a; echo $b; echo $c

nhưng không may, tôi đã có:

0
0
Sat Jun 28 21:28:03 CEST 2014: 3 4

Vì vậy, tôi không có bất kỳ thay đổi môi trường ở đây.

Làm thế nào để lưu đầu ra của lệnh (không chỉ chức năng) vào biến và lưu các thay đổi môi trường?

Tôi biết rằng $(...)mở subshell mới và đó là vấn đề, nhưng nó có thể làm một số cách giải quyết?


Phải, vì vậy vấn đề là $a$blà các biến cục bộ trong fhàm của bạn . Bạn có thể exporthọ, nhưng điều đó có vẻ sơ sài.
HalosGhost

1
@HalosGhost: Không, tôi không nghĩ vậy. Nhìn vào ví dụ đầu tiên: …; f; echo $a; …kết quả 3là bị lặp lại, do đó, fsửa đổi một biến shell (và không chỉ biến cục bộ của chính nó).
Scott

Câu trả lời:


2

Nếu bạn đang sử dụng Bash 4 trở lên, bạn có thể sử dụng các bộ đồng xử lý :

function f () { a=3; b=4 ; echo "`date`: $a $b"; }
coproc cat
f >&${COPROC[1]}
exec {COPROC[1]}>&-
read c <&${COPROC[0]}
echo a $a
echo b $b
echo c $c

sẽ xuất

a 3
b 4
c Sun Jun 29 10:08:15 NZST 2014: 3 4

coproctạo một quy trình mới chạy một lệnh đã cho (ở đây, cat). Nó lưu PID vào COPROC_PIDvà mô tả tệp đầu vào / đầu vào tiêu chuẩn thành một mảng COPROC(giống như pipe(2), hoặc xem tại đây hoặc tại đây ).

Ở đây chúng tôi chạy hàm với đầu ra tiêu chuẩn chỉ vào bộ đồng xử lý của chúng tôi đang chạy cat, và sau đó readtừ nó. Vì catchỉ cần nhổ đầu ra của nó trở lại, chúng ta có được đầu ra của hàm vào biến của chúng ta. exec {COPROC[1]}>&-chỉ cần đóng bộ mô tả tập tin để catkhông phải chờ đợi mãi mãi.


Lưu ý rằng readchỉ mất một dòng tại một thời điểm. Bạn có thể sử dụng mapfileđể có được một mảng các dòng hoặc chỉ sử dụng bộ mô tả tệp tuy nhiên bạn muốn sử dụng nó theo một cách khác.

exec {COPROC[1]}>&-hoạt động trong các phiên bản hiện tại của Bash, nhưng các phiên bản 4-series trước đó yêu cầu bạn lưu bộ mô tả tệp vào một biến đơn giản trước tiên : fd=${COPROC[1]}; exec {fd}>&-. Nếu biến của bạn không được đặt, nó sẽ đóng đầu ra tiêu chuẩn.


Nếu bạn đang sử dụng phiên bản 3 sê-ri của Bash, bạn có thể có được hiệu ứng tương tự mkfifo, nhưng sẽ không tốt hơn nhiều so với sử dụng một tệp thực tế tại thời điểm đó.


Tôi chỉ có thể sử dụng bash 3.1. Các ống rất khó sử dụng. Có mktemp và là lựa chọn duy nhất? Không có ở đây cú pháp hoặc tùy chọn đặc biệt để chạy lệnh và nhận đầu ra nhưng với thay đổi môi trường?
faramir

Bạn có thể sử dụng mkfifo để tạo một đường ống có tên thay thế cho coproc, điều này không liên quan đến việc thực sự ghi dữ liệu vào đĩa. Với điều đó, bạn sẽ chuyển hướng đầu ra đến fifo và nhập từ nó thay vì đồng xử lý. Nếu không thì không.
Michael Homer

+1 để điều chỉnh câu trả lời của tôi không sử dụng tệp. Nhưng, trong các thử nghiệm của tôi, exec {COPROC[1]}>&-lệnh (ít nhất là đôi khi) chấm dứt đồng xử lý ngay lập tức , do đó đầu ra của nó không còn khả dụng để đọc. coproc { cat; sleep 1; }dường như làm việc tốt hơn (Bạn có thể cần tăng 1nếu bạn đang đọc nhiều hơn một dòng.)
Scott

1
Có một ý nghĩa thông thường, được hiểu rõ về "tập tin thực tế" được sử dụng ở đây mà bạn cố tình coi thường.
Michael Homer

1
"Tập tin thực tế" có nghĩa là một tập tin trên đĩa , như mọi người ở đây hiểu, ngoại trừ, được cho là, bạn. Không ai từng ngụ ý hoặc cho phép một người đọc hợp lý suy luận rằng một trong số họ giống như các đối số hoặc biến môi trường. Tôi không quan tâm đến việc tiếp tục cuộc thảo luận này.
Michael Homer

2

Nếu bạn đã tính trên cơ thể fthực thi trong cùng một trình bao với người gọi, và do đó có thể sửa đổi các biến như ab, tại sao không làm cho hàm cũng được đặt c? Nói cách khác:

$ function f () { a=3; b=4 ; c=$(echo "$(date): $a $b"); }
$ a=0; b=0; f; echo $a; echo $b; echo $c
3
4
Mon Jun 30 14:32:00 EDT 2014: 3 4

Một lý do có thể là biến đầu ra cần phải có các tên khác nhau (c1, c2, v.v.) khi được gọi ở những nơi khác nhau, nhưng bạn có thể giải quyết điều đó bằng cách đặt hàm c_TEMP và yêu cầu người gọi thực hiện, c1=$c_TEMPv.v.


1

Đó là một kluge, nhưng hãy thử

f > c.file
c=$(cat c.file)

(và, tùy chọn, rm c.file).


Cảm ơn bạn. Đó là câu trả lời đơn giản, và hoạt động, nhưng có một số nhược điểm. Tôi biết rằng tôi có thể sử dụng mktemp, nhưng tôi không muốn tạo thêm tệp.
faramir

1

Chỉ cần gán tất cả các biến và viết đầu ra cùng một lúc.

f() { c= ; echo "${c:=$(date): $((a=3)) $((b=4))}" ; }

Bây giờ nếu bạn làm:

f ; echo "$a $b $c"

Đầu ra của bạn là:

Tue Jul  1 04:58:17 PDT 2014: 3 4
3 4 Tue Jul  1 04:58:17 PDT 2014: 3 4

Lưu ý rằng đây là mã di động POSIX đầy đủ. Tôi bước đầu thiết lập c=đến ''chuỗi null vì mở rộng tham số hạn chế chuyển nhượng biến đồng thời + đánh giá cho một trong hai chỉ số (như $((var=num))) hoặc từ null hoặc không tồn tại giá trị - hoặc, nói cách khác, bạn có thể không được kiêm nhiệm thiết lập đánh giá một biến cho một chuỗi tùy ý nếu biến đó đã được gán một giá trị. Vì vậy, tôi chỉ đảm bảo rằng nó trống trước khi thử. Nếu tôi không trống ctrước khi cố gắng gán nó, việc mở rộng sẽ chỉ trả về giá trị cũ.

Chỉ để chứng minh:

sh -c '
    c=oldval a=1
    echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2

newvalkhông giao cho $cinline vì oldvalđược mở rộng sang ${word}, trong khi các inline $((số học =phân ))luôn luôn xảy ra. Nhưng nếu $c không có oldval và trống hoặc không đặt ...

sh -c '
    c=oldval a=1
    echo ${c:=newval} $((a=a+a)) 
    c= a=$((a+a))
    echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2
newval 8

... sau đó newvalngay lập tức được gán và mở rộng thành $c.

Tất cả các cách khác để làm điều này liên quan đến một số hình thức đánh giá thứ cấp. Ví dụ: giả sử tôi muốn gán đầu ra f()cho một biến có tên nametại một điểm và vartại một điểm khác. Như được viết hiện tại, điều này sẽ không hoạt động mà không đặt var trong phạm vi của người gọi. Một cách khác có thể trông như thế này, mặc dù:

f(){ fout_name= fout= set -- "${1##[0-9]*}" "${1%%*[![:alnum:]]*}"
    (: ${2:?invalid or unspecified param - name set to fout}) || set --
    export "${fout_name:=${1:-fout}}=${fout:=$(date): $((a=${a:-50}+1)) $((b=${b:-100}-4))}"
    printf %s\\n "$fout"
}

f &&
    printf %s\\n \
        "$fout_name" \
        "$fout" \
        "$a" "$b"

Tôi đã cung cấp một ví dụ được định dạng tốt hơn bên dưới, nhưng, được gọi như trên đầu ra là:

sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul  2 02:27:07 PDT 2014: 51 96
fout
Wed Jul  2 02:27:07 PDT 2014: 51 96
51
96

Hoặc với các $ENVđối số hoặc đối số khác nhau :

b=9 f myvar &&
    printf %s\\n \
        "$fout_name" \
        "$fout" \
        "$myvar" \
        "$a" "$b"

###OUTPUT###

Tue Jul  1 19:56:42 PDT 2014: 52 5
myvar
Tue Jul  1 19:56:42 PDT 2014: 52 5
Tue Jul  1 19:56:42 PDT 2014: 52 5
52
5

Có lẽ điều khó nhất để có được đúng khi đánh giá hai lần là đảm bảo rằng các biến không phá vỡ dấu ngoặc kép và thực thi mã ngẫu nhiên. Càng nhiều lần một biến được đánh giá càng khó. Mở rộng tham số giúp rất nhiều ở đây, và sử dụng exporttrái ngược với evalan toàn hơn nhiều.

Trong ví dụ trên f()đầu tiên gán $foutcác ''chuỗi rỗng và sau đó đặt params vị trí để kiểm tra cho tên biến hợp lệ. Nếu cả hai bài kiểm tra không vượt qua, một thông báo sẽ được phát ra stderrvà giá trị mặc định foutđược gán cho $fout_name. Tuy nhiên, bất kể các thử nghiệm $fout_nameluôn được gán cho một fouthoặc tên bạn chỉ định $foutvà, tùy ý, tên được chỉ định của bạn luôn được gán giá trị đầu ra của hàm. Để chứng minh điều này tôi đã viết forvòng lặp nhỏ này :

for v in var '' "wr;\' ong"
    do sleep 10 &&
        a=${a:+$((a*2))} f "$v" || break
    echo "${v:-'' #null}" 
    printf '#\t"$%s" = '"'%s'\n" \
        a "$a" b "$b" \
        fout_name "$fout_name" \
        fout "$fout" \
        '(eval '\''echo "$'\''"$fout_name"\")' \
            "$(eval 'echo "$'"$fout_name"\")"
 done

Nó chơi xung quanh một số với tên biến và mở rộng tham số. Nếu bạn có một câu hỏi, hãy hỏi. Điều đó chỉ chạy cùng một vài dòng trong hàm đã được trình bày ở đây. Ít nhất nên đề cập đến việc mặc dù rằng các biến $a$bhành vi khác nhau tùy thuộc vào việc chúng được xác định tại lệnh gọi hay đã được đặt. Tuy nhiên, forhầu như không có gì ngoài định dạng tập dữ liệu và được cung cấp bởi f(). Có một cái nhìn:

###OUTPUT###

Wed Jul  2 02:50:17 PDT 2014: 51 96
var
#       "$a" = '51'
#       "$b" = '96'
#       "$fout_name" = 'var'
#       "$fout" = 'Wed Jul  2 02:50:17 PDT 2014: 51 96'
#       "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul  2 02:50:17 PDT 2014: 51 96'
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul  2 02:50:27 PDT 2014: 103 92
'' #null
#       "$a" = '103'
#       "$b" = '92'
#       "$fout_name" = 'fout'
#       "$fout" = 'Wed Jul  2 02:50:27 PDT 2014: 103 92'
#       "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul  2 02:50:27 PDT 2014: 103 92'
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul  2 02:50:37 PDT 2014: 207 88
wr;\' ong
#       "$a" = '207'
#       "$b" = '88'
#       "$fout_name" = 'fout'
#       "$fout" = 'Wed Jul  2 02:50:37 PDT 2014: 207 88'
#       "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul  2 02:50:37 PDT 2014: 207 88'
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.