Bài tập biến ảnh hưởng đến trình chạy hiện tại


8

Trong khi viết một số mã tôi phát hiện ra rằng dòng này:

$ TZ="America/Los_Angeles"       date; echo "$TZ"
Thu Dec 24 14:39:15 PST 2015

Chính xác đưa ra thời gian thực tế tại "Los Angeles" và giá trị của biến TZkhông được giữ lại. Tất cả như dự kiến.

Tuy nhiên, với dòng này, mà tôi đã sử dụng để mở rộng một số định dạng và về cơ bản thực hiện điều tương tự, vẫn giữ nguyên giá trị của TZ:

TZ="America/Los_Angeles" eval  date; echo "$TZ"
Thu Dec 24 14:41:34 PST 2015
America/Los_Angeles

Sau nhiều thử nghiệm, tôi phát hiện ra rằng điều này chỉ xảy ra ở một số vỏ. Nó xảy ra trong dash, ksh nhưng không phải trong bash hoặc zsh.

Q

Câu hỏi là:

  • Tại sao giá trị của TZ được giữ lại trong vỏ hiện tại?
  • Làm thế nào mà có thể tránh / kiểm soát (nếu có thể)?

Bổ sung.

Tôi đã chạy thử nghiệm trong một số shell với hai dòng này:

myTZ="America/Los_Angeles"
unset TZ; { TZ="$myTZ"      date; } >/dev/null; echo -n "  direct $TZ"
unset TZ; { TZ="$myTZ" eval date; } >/dev/null; echo    "  evaled $TZ"

Và kết quả này:

/bin/ash        :   direct   evaled America/Los_Angeles
/bin/dash       :   direct   evaled America/Los_Angeles
/bin/sh         :   direct   evaled America/Los_Angeles
/bin/bash       :   direct   evaled
/bin/ksh93      :   direct   evaled America/Los_Angeles
/bin/lksh       :   direct   evaled America/Los_Angeles
/bin/mksh       :   direct   evaled America/Los_Angeles
/bin/zsh        :   direct   evaled
/bin/zsh4       :   direct   evaled 

Giá trị TZ ảnh hưởng đến shell đang chạy trong tất cả các shell ngoại trừ bash và zsh.

Câu trả lời:


6

Như bạn đã tìm thấy, đó là hành vi cụ thể. Nhưng nó cũng có ý nghĩa.

Giá trị được giữ lại trong môi trường của shell vì lý do tương tự giá trị của các biến môi trường khác được giữ lại bởi các lệnh khác khi bạn định nghĩa tiền tố cho các dòng lệnh của chúng - bạn đang đặt các biến trong môi trường của chúng.

Các nội trang đặc biệt nói chung là đa dạng nội tại nhất trong bất kỳ hệ vỏ nào - evalvề cơ bản là một tên có thể truy cập được đối với trình phân tích cú pháp của trình bao, settheo dõi và định cấu hình các tùy chọn vỏ và tham số hệ vỏ, return/ break/ continueluồng điều khiển vòng lặp kích hoạt, trapxử lý tín hiệu, execmở / đóng tệp. Đây là tất cả các tiện ích cơ bản - và thường được thực hiện với các hàm bao bọc hầu như không có trên thịt và khoai tây của vỏ của bạn.

Thực thi hầu hết các lệnh liên quan đến một số môi trường phân lớp - môi trường lớp con (không nhất thiết phải là một quy trình riêng biệt) - mà bạn không nhận được khi gọi các nội dung đặc biệt. Vì vậy, khi bạn đặt môi trường cho một trong các lệnh này, bạn đặt môi trường cho trình bao của mình. Bởi vì về cơ bản chúng đại diện cho vỏ của bạn.

Nhưng chúng không phải là các lệnh duy nhất duy trì môi trường theo cách đó - các hàm cũng làm như vậy. Và các lỗi hoạt động khác nhau đối với các tích hợp đặc biệt - thử cat <doesntexistvà sau đó thử exec <doesntexisthoặc thậm chí chỉ : <doesntexistvà trong khi catlệnh sẽ khiếu nại, exechoặc :sẽ giết lớp vỏ POSIX. Điều tương tự cũng đúng với các lỗi mở rộng trên dòng lệnh của họ. Về cơ bản, chúng là vòng lặp chính .

Các lệnh này không phải giữ lại môi trường - một số shell bao bọc bên trong của chúng chặt chẽ hơn các lệnh khác, làm lộ ít chức năng cốt lõi hơn và thêm nhiều bộ đệm hơn giữa lập trình viên và giao diện. Những chiếc vỏ tương tự này cũng có thể có xu hướng chậm hơn một chút so với những cái khác. Chắc chắn họ yêu cầu rất nhiều điều chỉnh phi tiêu chuẩn để làm cho chúng phù hợp với thông số kỹ thuật. Và dù sao, nó không phải là một điều xấu :

fn(){ bad_command || return=$some_value return; }

Những thứ đó thật dễ dàng . Làm thế nào khác bạn sẽ duy trì sự trở lại của bad_commandđơn giản như vậy mà không cần phải thiết lập một loạt các môi trường bổ sung mà vẫn làm bài tập một cách có điều kiện?

arg=$1 shift; x=$y unset y

Những thứ đó cũng hoạt động. Trao đổi tại chỗ đơn giản hơn.

IFS=+  set -- "$IFS" x y z
x="$*" IFS=$1 shift
echo "${x#"$IFS"}" "$*"

+x+y+z x y z

...hoặc là...

expand(){
    PS4="$*" set -x "" "$PS4" 
    { $1; }  2>&1
    PS4=$2   set +x
}   2>/dev/null

x='echo kill my computer; $y'
y='haha! just kidding!' expand "${x##*[\`\(]*}"

... là một cái khác tôi thích sử dụng ...

echo kill my computer; haha! just kidding!

@BinaryZebra - nhưng vấn đề là chúng không hoạt động khác nhau - khi bạn đặt biến cho một số lệnh khác, chúng vẫn tồn tại trong môi trường thực thi khác. khi bạn đặt biến trong môi trường shell của bạn, chúng cũng tồn tại.
mikeerv

3

Nó chỉ ra rằng có một lý do rất cụ thể cho hành vi này.
Mô tả về những gì xảy ra là một chút lâu hơn.

Chỉ có bài tập.

Một dòng lệnh được thực hiện (chỉ) các bài tập sẽ đặt các biến cho shell này .

$ unset a b c d
$ a=b c=d
$ echo "<$a::$c>"
<b::d>

Giá trị của các bình được gán sẽ được giữ lại.

Lệnh ngoài.

Các nhiệm vụ trước một lệnh bên ngoài chỉ đặt các biến cho shell đó :

$ unset a b c d
$ a=b c=d bash -c 'echo "one:|$c|"'; echo "two:<$c>"
one:|d|
two:<>

Và tôi có nghĩa là "bên ngoài" như bất kỳ lệnh nào phải được tìm kiếm trong PATH.

Điều này cũng áp dụng cho các tích hợp thông thường (ví dụ như cd):

$ unset a b c d; a=b c=d cd . ; echo "<$a::$c>"
<::>

Cho đến đây tất cả là như thường được mong đợi.

Được xây dựng đặc biệt.

Nhưng đối với các phần dựng sẵn đặc biệt, POSIX yêu cầu các giá trị được đặt cho lớp vỏ này .

  1. Các bài tập biến được chỉ định với các tiện ích tích hợp đặc biệt vẫn có hiệu lực sau khi hoàn thành tích hợp.
$ sh -c 'unset a b c d; a=b c=d export f=g ; echo "<$a::$c::$f>"'
<b::d::g>

Tôi đang sử dụng một cuộc gọi để shgiả sử rằng đó shlà một vỏ tương thích POSIX.

Đây không phải là một cái gì đó thường được sử dụng.

Điều này có nghĩa là các phép gán đặt trước bất kỳ danh sách dựng sẵn đặc biệt nào sẽ giữ lại các giá trị được gán trong lớp vỏ đang chạy hiện tại:

break : continue . eval exec exit export 
readonly return set shift times trap unset

Điều này sẽ xảy ra nếu một vỏ hoạt động theo thông số POSIX.

Phần kết luận:

Có thể đặt các biến cho chỉ một lệnh, bất kỳ lệnh nào, bằng cách đảm bảo lệnh không phải là một tích hợp đặc biệt. Lệnh commandnày là một nội dung thông thường. Nó chỉ báo cho shell sử dụng một lệnh chứ không phải hàm. Dòng này hoạt động trong tất cả các shell (trừ ksh93):

$ unset a b c d; a=b c=d command eval 'f=g'; echo "<$a::$c::$f>"
<::::g>

Trong trường hợp như vậy, vars a và b được đặt cho môi trường của lệnh lệnh và bị loại bỏ sau đó.

Thay vào đó, điều này sẽ giữ lại các giá trị được gán (trừ bash và zsh):

$ unset a b c d; a=b c=d eval 'f=g'; echo "<$a::$c::$f>"
<b::d::g>

Lưu ý rằng việc chuyển nhượng sau eval được trích dẫn duy nhất để bảo vệ nó khỏi các mở rộng không mong muốn.

Vì vậy: Để đặt các biến trong môi trường lệnh sử dụng command eval:

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.