Tại sao tôi không thể chỉ định một biến môi trường và lặp lại nó trong cùng một dòng lệnh?


91

Hãy xem xét đoạn mã này:

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz

Ở đây tôi đã thiết $SOMEVARđể AAAtrên dòng đầu tiên - và khi tôi echo nó trên dòng thứ hai, tôi nhận được AAAnội dung như mong đợi.

Nhưng sau đó, nếu tôi cố gắng chỉ định biến trên cùng một dòng lệnh với echo:

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz

... Tôi không nhận được BBBnhư tôi mong đợi - Tôi nhận được giá trị cũ ( AAA).

Đây có phải là cách mọi thứ được cho là? Nếu vậy, tại sao bạn có thể chỉ định các biến như thế nào LD_PRELOAD=/... program args ...và nó có hoạt động không? Tôi đang thiếu gì?


2
Nó hoạt động khi bạn thực hiện việc gán một câu lệnh riêng biệt hoặc khi gọi một tập lệnh với môi trường riêng của nó, nhưng không hoạt động khi bắt đầu một lệnh trong môi trường hiện tại. Hấp dẫn!
Todd A. Jacobs,

1
Lý do LD_PRELOADhoạt động là biến được đặt trong môi trường của chương trình - không phải trên dòng lệnh của nó.
Tạm dừng cho đến khi có thông báo mới.

Câu trả lời:


103

Những gì bạn thấy là hành vi mong đợi. Rắc rối là shell cha đánh giá $SOMEVARtrên dòng lệnh trước khi nó gọi lệnh với môi trường đã sửa đổi. Bạn cần phải nhận được đánh giá $SOMEVARhoãn lại cho đến khi môi trường được thiết lập.

Các tùy chọn tức thì của bạn bao gồm:

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz.
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'.

Cả hai đều sử dụng dấu nháy đơn để ngăn trình bao mẹ đánh giá $SOMEVAR; nó chỉ được đánh giá sau khi nó được đặt trong môi trường (tạm thời, trong khoảng thời gian của một lệnh).

Một tùy chọn khác là sử dụng ký hiệu vỏ con (cũng được Marcus Kuhn đề xuất trong câu trả lời của mình ):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

Biến chỉ được đặt trong sub-shell


Tuyệt vời, @JonathanLeffler - rất cảm ơn bạn đã giải thích; chúc mừng!
sdaau

Việc bổ sung từ @ markus-kuhn rất khó để đánh giá quá cao.
Alex Che

37

Vấn đề, được xem lại

Thành thật mà nói, hướng dẫn sử dụng là khó hiểu về điểm này. Các thủ GNU Bash nói:

Môi trường cho bất kỳ lệnh hoặc chức năng đơn giản nào [lưu ý rằng điều này không bao gồm nội trang] có thể được tăng cường tạm thời bằng cách thêm tiền tố cho nó với các phép gán tham số, như được mô tả trong Tham số Shell. Các câu lệnh gán này chỉ ảnh hưởng đến môi trường được thấy bởi lệnh đó.

Nếu bạn thực sự phân tích cú pháp câu, điều nó nói là môi trường cho lệnh / hàm được sửa đổi, nhưng không phải là môi trường cho tiến trình mẹ. Vì vậy, điều này sẽ hoạt động:

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb

bởi vì môi trường cho lệnh env đã được sửa đổi trước khi nó được thực thi. Tuy nhiên, điều này sẽ không hoạt động:

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc

vì khi mở rộng tham số được thực hiện bởi shell.

Các bước thông dịch viên

Một phần khác của vấn đề là Bash xác định các bước sau cho trình thông dịch của nó:

  1. Đọc đầu vào của nó từ một tệp (xem Tập lệnh Shell), từ một chuỗi được cung cấp dưới dạng đối số cho tùy chọn gọi -c (xem Dấu gạch ngang mời) hoặc từ thiết bị đầu cuối của người dùng.
  2. Ngắt đầu vào thành các từ và toán tử, tuân theo các quy tắc trích dẫn được mô tả trong Trích dẫn. Các mã thông báo này được phân tách bằng ký tự siêu. Mở rộng bí danh được thực hiện theo bước này (xem Bí danh).
  3. Phân tích cú pháp mã thông báo thành các lệnh đơn giản và phức hợp (xem Lệnh Shell).
  4. Thực hiện các mở rộng shell khác nhau (xem Mở rộng Shell), chia các mã thông báo được mở rộng thành danh sách các tên tệp (xem Mở rộng Tên tệp) và các lệnh và đối số.
  5. Thực hiện mọi chuyển hướng cần thiết (xem Chuyển hướng) và xóa các toán tử chuyển hướng và toán hạng của chúng khỏi danh sách đối số.
  6. Thực thi lệnh (xem Thực thi lệnh).
  7. Tùy chọn đợi lệnh hoàn thành và thu thập trạng thái thoát của nó (xem Trạng thái thoát).

Điều đang xảy ra ở đây là các nội trang không có được môi trường thực thi của riêng chúng, vì vậy chúng không bao giờ thấy môi trường được sửa đổi. Bên cạnh đó, các lệnh đơn giản (ví dụ như / bin / echo) làm được một ennvironment sửa đổi (đó là lý do ví dụ env làm việc) nhưng việc mở rộng vỏ đang diễn ra trong hiện tại môi trường trong bước # 4.

Nói cách khác, bạn không chuyển 'aaa $ TESTVAR ccc' tới / bin / echo; bạn đang chuyển chuỗi nội suy (như được mở rộng trong môi trường hiện tại) đến / bin / echo. Trong trường hợp này, vì môi trường hiện tại không có TESTVAR , bạn chỉ cần chuyển 'aaa ccc' vào lệnh.

Tóm lược

Tài liệu có thể rõ ràng hơn rất nhiều. Điều tốt là có Stack Overflow!

Xem thêm

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Enosystem


Tôi đã ủng hộ điều này - nhưng tôi vừa quay lại câu hỏi này, và bài đăng này chứa chính xác các gợi ý mà tôi cần; cảm ơn rất nhiều, @CodeGnome!
sdaau

Tôi không biết liệu Bash có thay đổi trong lĩnh vực này kể từ khi câu trả lời này được đăng hay không, nhưng các phép gán biến tiền tố hiện hoạt động với nội trang. Ví dụ, FOO=foo eval 'echo $FOO'bản in foonhư mong đợi. Điều này có nghĩa là bạn có thể làm những việc như IFS="..." read ....
Will Vousden

Tôi nghĩ những gì đang xảy ra là Bash thực sự sửa đổi môi trường của chính nó tạm thời và khôi phục nó sau khi lệnh hoàn thành, điều này có thể gây ra những tác dụng phụ kỳ lạ.
Will Vousden

22

Để đạt được những gì bạn muốn, hãy sử dụng

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

Lý do:

  • Bạn phải phân tách lệnh gán bằng dấu chấm phẩy hoặc dòng mới khỏi lệnh tiếp theo, nếu không lệnh đó không được thực thi trước khi việc mở rộng tham số xảy ra cho lệnh tiếp theo (echo).

  • Bạn cần thực hiện nhiệm vụ bên trong môi trường vỏ con để đảm bảo rằng nó không tồn tại ngoài dòng hiện tại.

Giải pháp này ngắn hơn, gọn gàng hơn và hiệu quả hơn so với một số giải pháp khác được đề xuất, đặc biệt nó không tạo ra một quy trình mới.


3
Đối với những người tìm kiếm google tương lai ở đây: Đây có lẽ là câu trả lời tốt nhất cho câu hỏi này. Để làm phức tạp thêm, nếu bạn cần lệnh gán sẵn có trong môi trường của lệnh, bạn cần xuất nó. Vỏ con vẫn ngăn không cho phép gán vẫn tồn tại. (export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')")
eaj

@eaj Để xuất một biến vỏ để một cuộc gọi chương trình bên ngoài duy nhất, như trong ví dụ của bạn, chỉ cần sử dụngSOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')"
Markus Kuhn

10

Lý do là điều này đặt một biến môi trường cho một dòng. Nhưng, echokhông làm mở rộng, bashkhông. Do đó, biến của bạn là thực sự mở rộng trước khi lệnh được thực thi, mặc dù SOME_VARBBBtrong bối cảnh lệnh echo.

Để xem hiệu quả, bạn có thể làm như sau:

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB

Ở đây biến không được mở rộng cho đến khi tiến trình con thực thi, vì vậy bạn sẽ thấy giá trị được cập nhật. nếu bạn kiểm tra SOME_VARIABLElại trong shell gốc, nó vẫn AAAnhư mong đợi.


1
+1 để được giải thích chính xác về lý do tại sao nó không hoạt động như đã viết và để có một giải pháp khả thi.
Jonathan Leffler

1
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

Sử dụng một ; để tách các câu lệnh nằm trên cùng một dòng.


1
Điều đó hoạt động, nhưng không hoàn toàn là vấn đề. Ý tưởng là thiết lập môi trường chỉ cho một lệnh, không phải vĩnh viễn như giải pháp của bạn.
Jonathan Leffler

Cảm ơn vì điều đó @Kyros; Không biết tại sao tôi lại bỏ lỡ điều đó đến bây giờ :) Vẫn còn lang thang làm thế nào LD_PRELOADvà như vậy có thể hoạt động trước tệp thực thi mà không có dấu chấm phẩy, mặc dù ... Rất cảm ơn một lần nữa - chúc mừng!
sdaau

@JonathanLeffler - thực sự, đó là ý tưởng; Tôi không nhận ra dấu chấm phẩy làm cho thay đổi vĩnh viễn - cảm ơn bạn đã lưu ý điều đó!
sdaau

1

Đây là một giải pháp thay thế:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz

Cho dù bạn sử dụng &&hay ;để tách các lệnh, việc gán vẫn tồn tại, đây không phải là hành vi mong muốn của OP. Markus Kuhn có phiên bản chính xác của câu trả lời này.
eaj
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.