Phạm vi có thể biến vỏ có phạm vi gì?


42

Tôi vừa gặp phải một vấn đề cho tôi thấy tôi không rõ về phạm vi của các biến shell.

Tôi đã cố gắng sử dụng bundle install, đó là một lệnh Ruby sử dụng giá trị của $GEM_HOMEđể thực hiện công việc của nó. Tôi đã thiết lập $GEM_HOME, nhưng lệnh bỏ qua giá trị đó cho đến khi tôi sử dụng export, như trong export GEM_HOME=/some/path.

Tôi đọc rằng điều này làm cho biến bằng cách nào đó "toàn cầu" (còn được gọi là biến môi trường ), nhưng tôi không hiểu điều đó có nghĩa là gì. Tôi biết về toàn cầu trong lập trình, nhưng không phải qua các chương trình riêng biệt.

Ngoài ra, do cài đặt của tôi các biến như vậy chỉ áp dụng cho phiên shell hiện tại, làm thế nào tôi có thể đặt chúng cho một quy trình được tạo ra?

Phạm vi có thể biến vỏ có phạm vi gì?

Câu trả lời:


33

Các quy trình được tổ chức như một cây: mỗi quá trình có cha mẹ độc đáo, ngoài initđó PIDluôn là 1 và không có cha mẹ.

Việc tạo ra một quy trình mới thường đi qua một cặp các cuộc gọi fork/ execvhệ thống, trong đó môi trường của quy trình con là một bản sao của quy trình cha.

Để đặt một biến trong môi trường từ shell, bạn phải exportbiến nó, để nó có thể nhìn thấy đệ quy cho tất cả trẻ em. Nhưng hãy lưu ý rằng nếu một đứa trẻ thay đổi giá trị của một biến, thì giá trị thay đổi chỉ hiển thị với nó và tất cả các quy trình được tạo sau thay đổi đó (là bản sao , như đã nói trước đó).

Cũng tính đến việc một tiến trình con có thể thay đổi môi trường của nó, ví dụ có thể đặt lại nó về các giá trị mặc định, như có thể được thực hiện từ loginví dụ.


1
Ah! OK, hãy xem tôi có hiểu điều này không. Trong shell, nếu tôi nói FOO=bar, điều đó đặt giá trị cho quá trình shell hiện tại. Nếu sau đó tôi chạy một chương trình như ( bundle install), thì nó tạo ra một tiến trình con, không có quyền truy cập FOO. Nhưng nếu tôi đã nói export FOO=bar, quá trình con (và con cháu của nó) sẽ có quyền truy cập vào nó. Đến lượt mình, một trong số họ có thể gọi export FOO=buzzđể thay đổi giá trị cho con cháu của nó hoặc chỉ FOO=buzzthay đổi giá trị cho chính nó. Điều đó có đúng không?
Nathan Long

2
@NathanLong Điều đó không chính xác: trong tất cả các shell hiện đại, một biến được xuất (và do đó, bất kỳ thay đổi nào về giá trị được phản ánh trong môi trường của con cháu) hoặc không được xuất (có nghĩa là biến đó không nằm trong môi trường). Cụ thể, nếu biến đã có trong môi trường khi shell khởi động, nó được xuất.
Gilles 'SO- ngừng trở nên xấu xa'

2
Tôi đã có một chút bối rối bởi câu "nếu một đứa trẻ thay đổi giá trị của một biến, thì giá trị thay đổi chỉ hiển thị với nó và tất cả các quy trình được tạo sau thay đổi đó". Sẽ đúng hơn khi nói "... hiển thị với nó và tất cả các quá trình hậu duệ của nó được tạo ra sau thay đổi đó" - những đứa trẻ khác của quy trình cha mẹ, ngay cả những quá trình bắt đầu sau quá trình con, không bị ảnh hưởng.
Jaan

26

Ít nhất là dưới kshbash, các biến có thể có ba phạm vi, không phải hai như tất cả các câu trả lời còn lại hiện đang nói.

Ngoài biến phạm vi xuất khẩu (tức là môi trường) và phạm vi biến không được trình bày, còn có một phạm vi hẹp thứ ba cho các biến cục bộ của hàm.

Các biến được khai báo trong các hàm shell với typesetmã thông báo chỉ hiển thị bên trong các hàm mà chúng được khai báo trong và các hàm (phụ) được gọi từ đó.

ksh/ này bash:

# Create a shell script named /tmp/show that displays the scoped variables values.    
echo 'echo [$environment] [$shell] [$local]' > /tmp/show
chmod +x /tmp/show

# Function local variable declaration
function f
{
    typeset local=three
    echo "in function":
    . /tmp/show 
}

# Global variable declaration
export environment=one

# Unexported (i.e. local) variable declaration
shell=two

# Call the function that creates a function local variable and
# display all three variable values from inside the function
f

# Display the three values from outside the function
echo "in shell":
. /tmp/show 

# Display the same values from a subshell
echo "in subshell":
/tmp/show

# Display the same values from a disconnected shell (simulated here by a clean environment start)
echo "in other shell"
env -i /tmp/show 

tạo đầu ra này:

in function:
[one] [two] [three]
in shell:
[one] [two] []
in subshell:
[one] [] []
in other shell
[] [] []

Như bạn có thể thấy, biến được xuất được hiển thị từ ba vị trí đầu tiên, các biến không được xuất hiện không được hiển thị bên ngoài lớp vỏ hiện tại và biến cục bộ của hàm không có giá trị bên ngoài hàm. Thử nghiệm cuối cùng cho thấy không có giá trị nào cả, điều này là do các biến xuất khẩu không được chia sẻ giữa các shell, tức là chúng chỉ có thể được kế thừa và giá trị được kế thừa không thể bị ảnh hưởng bởi shell cha.

Lưu ý rằng hành vi sau này khá khác so với Windows, nơi bạn có thể sử dụng Biến hệ thống hoàn toàn toàn cầu và được chia sẻ bởi tất cả các quy trình.


12

Chúng được phân chia theo quy trình

Những người trả lời khác đã giúp tôi hiểu rằng phạm vi biến vỏ là về các quá trình và hậu duệ của họ .

Khi bạn nhập một lệnh như lstrên dòng lệnh, bạn thực sự đang tiến hành một quy trình để chạy lschương trình. Quá trình mới có vỏ của bạn là cha mẹ của nó.

Bất kỳ quá trình nào cũng có thể có các biến "cục bộ" riêng, không được truyền cho các tiến trình con. Nó cũng có thể đặt các biến "môi trường". Sử dụng exporttạo ra một biến môi trường. Trong cả hai trường hợp, các quá trình không liên quan (đồng đẳng của bản gốc) sẽ không thấy biến; chúng tôi chỉ kiểm soát những gì quá trình con nhìn thấy.

Giả sử bạn có bash shell, chúng ta sẽ gọi A. Bạn gõ bash, tạo ra bash shell con, chúng ta sẽ gọi B. Mọi thứ bạn gọi exporttrong A vẫn sẽ được đặt trong B.

Bây giờ, trong B, bạn nói FOO=b. Một trong hai điều sẽ xảy ra:

  • Nếu B không nhận được (từ A) một biến môi trường được gọi FOO, nó sẽ tạo một biến cục bộ. Con của B sẽ không nhận được (trừ khi B gọi export).
  • Nếu B đã nhận (từ A) một biến môi trường được gọi là FOOchính nó, nó sẽ tự sửa đổi nó và các con sau đó của nó . Con của B sẽ thấy giá trị mà B gán. Tuy nhiên, điều này sẽ không ảnh hưởng đến A.

Đây là một bản demo nhanh.

FOO=a      # set "local" environment variable
echo $FOO  # 'a'
bash       # forks a child process for the new shell
echo $FOO  # not set
exit       # return to original shell
echo $FOO  # still 'a'

export FOO # make FOO an environment variable
bash       # fork a new "child" shell
echo $FOO  # outputs 'a'
FOO=b      # modifies environment (not local) variable
bash       # fork "grandchild" shell
echo $FOO  # outputs 'b'
exit       # back to child shell
exit       # back to original shell
echo $FOO  # outputs 'a'

Tất cả điều này giải thích vấn đề ban đầu của tôi: Tôi đặt GEM_HOMEtrong vỏ của mình, nhưng khi tôi gọi bundle install, điều đó đã tạo ra một quy trình con. Bởi vì tôi đã không sử dụng export, quá trình con không nhận được vỏ GEM_HOME.

Không xuất khẩu

Bạn có thể "hủy xuất" một biến - ngăn không cho nó được truyền cho trẻ em - bằng cách sử dụng export -n FOO.

export FOO=a   # Set environment variable
bash           # fork a shell
echo $FOO      # outputs 'a'
export -n FOO  # remove environment var for children
bash           # fork a shell
echo $FOO      # Not set
exit           # back up a level
echo $FOO      # outputs 'a' - still a local variable

1
Khi bạn nói "nó sẽ tự sửa đổi nó và con của nó", bạn nên làm rõ rằng chỉ những đứa trẻ được tạo ra sau khi sửa đổi mới thấy giá trị được sửa đổi.
enzotib

1
@enzotib - điểm tốt. Đã cập nhật.
Nathan Long

3

Giải thích tốt nhất tôi có thể tìm thấy về xuất khẩu là điều này:

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html

Biến được đặt trong vỏ con hoặc vỏ con chỉ hiển thị với vỏ con được xác định. Biến xuất khẩu thực sự được tạo thành một biến môi trường. Vì vậy, để rõ ràng, bạn bundle installthực thi shell của chính nó mà không nhìn thấy $GEM_HOMEtrừ khi nó được tạo một environmentbiến aka xuất khẩu.

Bạn có thể xem tài liệu về phạm vi biến ở đây:

http://www.tldp.org/LDP/abs/html/subshells.html


Ah, vì vậy tôi đã không chính xác khi sử dụng thuật ngữ "biến môi trường" cho FOO=bar; bạn phải sử dụng exportđể làm cho nó một. Câu hỏi sửa cho phù hợp.
Nathan Long

Hãy xem liên kết tôi đã thêm vào.
Karlson

3

Có một hệ thống phân cấp phạm vi thay đổi, như mong đợi.

Xung quanh

Phạm vi ngoài cùng là môi trường. Đây là phạm vi duy nhất được quản lý bởi hệ điều hành và do đó được đảm bảo tồn tại cho mọi quy trình. Khi một quá trình được bắt đầu, nó nhận được một bản sao môi trường của cha mẹ mà sau đó cả hai trở nên độc lập: sửa đổi môi trường của con không làm thay đổi cha mẹ và sửa đổi môi trường của cha mẹ không thay đổi môi trường của con hiện có.

Biến Shell

Shell có khái niệm riêng về các biến. Đây là nơi mọi thứ bắt đầu trở nên một chút khó hiểu.

Khi bạn gán một giá trị cho một biến trong shell và biến đó đã tồn tại trong môi trường, biến môi trường sẽ nhận giá trị mới. Tuy nhiên, nếu biến không có trong môi trường, nó sẽ trở thành biến shell . Biến Shell chỉ tồn tại trong quy trình shell, tương tự như cách biến Ruby chỉ tồn tại trong tập lệnh Ruby. Chúng không bao giờ được thừa hưởng bởi các quá trình con.

Đây là nơi mà exporttừ khóa phát huy tác dụng. Nó sao chép một biến shell vào môi trường của tiến trình shell để các tiến trình con có thể kế thừa.

Biến cục bộ

Biến cục bộ là biến shell nằm trong phạm vi khối mã chứa chúng. Bạn khai báo các biến cục bộ bằng typesettừ khóa (di động) hoặc localhoặc declare(Bash). Giống như các biến shell khác, các biến cục bộ không được kế thừa bởi các tiến trình con. Ngoài ra các biến cục bộ không thể được xuất khẩu.

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.