Làm thế nào tôi có thể hủy xuất một biến, mà không mất giá trị của nó?


10

Giả sử tôi đã xuất một biến:

foo=bar
export foo

Bây giờ, tôi muốn hủy xuất nó. Điều đó để nói, nếu tôi không sh -c 'echo "$foo"'nên nhận bar. fookhông nên xuất hiện trong sh -cmôi trường của tất cả. sh -cchỉ là một ví dụ, một cách dễ dàng để hiển thị sự hiện diện của một biến. Lệnh có thể là bất cứ điều gì - nó có thể là một cái gì đó mà hành vi của nó bị ảnh hưởng đơn giản bởi sự hiện diện của biến trong môi trường của nó.

Tôi có thể:

  1. unset biến và mất nó
  2. Loại bỏ nó bằng cách sử dụng envcho mỗi lệnh:env -u foo sh -c 'echo "$foo"'
    • không thực tế nếu bạn muốn tiếp tục sử dụng shell hiện tại trong một thời gian.

Lý tưởng nhất là tôi muốn giữ giá trị của biến, nhưng không để nó hiển thị ở tất cả trong một tiến trình con, thậm chí không phải là một biến trống.

Tôi đoán tôi có thể làm:

otherfoo="$foo"; unset foo; foo="$otherfoo"; unset otherfoo

Rủi ro này dậm chân otherfoo, nếu nó đã tồn tại.

Có phải đó là cách duy nhất? Có những cách tiêu chuẩn?


1
Bạn có thể lặp lại giá trị vào một tệp tạm thời, bằng cách sử dụng mktempnếu đủ khả năng di động và bỏ đặt giá trị và lấy nguồn tạm thời để gán biến. Ít nhất một tệp tạm thời có thể được tạo với một tên tùy ý ít nhiều trái ngược với biến shell.
Thomas Dickey

@Sukminder sh -cLệnh chỉ là một ví dụ. Thực hiện bất kỳ lệnh nào trong đó bạn không thể bỏ đặt một biến thay cho nó, nếu bạn muốn.
muru

Câu trả lời:


6

Không có cách tiêu chuẩn.

Bạn có thể tránh sử dụng một biến tạm thời bằng cách sử dụng một hàm. Hàm sau đây quan tâm đến việc giữ các biến unset unset và các biến rỗng. Tuy nhiên, nó không hỗ trợ các tính năng được tìm thấy trong một số shell như các biến chỉ đọc hoặc gõ.

unexport () {
  while [ "$#" -ne 0 ]; do
    eval "set -- \"\${$1}\" \"\${$1+set}\" \"\$@\""
    if [ -n "$2" ]; then
      unset "$3"
      eval "$3=\$1"
    fi
    shift; shift; shift
  done
}
unexport foo bar

Trong ksh, bash và zsh, bạn có thể hủy xuất một biến với typeset +x foo. Điều này bảo tồn các thuộc tính đặc biệt như các loại, vì vậy nên sử dụng nó. Tôi nghĩ rằng tất cả các vỏ có tích hợp typesetđều có typeset +x.

case $(LC_ALL=C type typeset 2>&1) in
  typeset\ *\ builtin) unexport () { typeset +x -- "$@"; };;
  *) unexport () {  };; # code above
esac

1
Đối với những người không quen thuộc ${var+foo}, nó ước tính foonếu varđược đặt, ngay cả khi trống và không có gì khác.
muru

Nói, bạn có bất kỳ ý kiến ​​về typeset +xvs export -ncho các shell hỗ trợ trước đây? Là export -nhiếm hơn, hoặc nó không bảo tồn một số tài sản?
muru

@muru Nếu bạn đang viết một tập lệnh bash, bạn có thể sử dụng export -nhoặc typeset +xthờ ơ. Trong ksh hoặc zsh, chỉ có typeset +x.
Gilles 'SO- đừng trở nên xấu xa'

7

EDIT: Đối với bashchỉ, như đã chỉ ra trong các ý kiến:

Các -ntùy chọn để exportloại bỏ các exporttài sản từ mỗi tên nhất định. (Xem help export.)

Vì vậy, đối với bash, lệnh bạn muốn là:export -n foo


1
Đó là của cụ shell (xem POSIX ), OP đã không chỉ định một vỏ, nhưng yêu cầu một tiêu chuẩn cách giải quyết vấn đề.
Thomas Dickey

1
@ThomasDickey, đã không nhận ra điều đó. Cảm ơn, cập nhật.
Wildcard

3

Tôi đã viết một hàm POSIX tương tự, nhưng điều này không có nguy cơ thực thi mã tùy ý:

unexport()
    while case ${1##[0-9]*} in                   ### rule out leading numerics
          (*[!_[:alnum:]]*|"")                   ### filter out bad|empty names
          set "" ${1+"bad name: '$1'"}           ### prep bad name error
          return ${2+${1:?"$2"}}                 ### fail w/ above err or return 
          esac
    do    eval  set '"$'"{$1+$1}"'" "$'"$1"'" "$'@\" ###  $1 = (  $1+ ? $1 : "" )
          eval  "${1:+unset $1;$1=\$2;} shift 3"     ### $$1 = ( $1:+ ? $2 : -- )
    done

Nó cũng sẽ xử lý nhiều đối số mà bạn quan tâm để cung cấp nó. Nếu một đối số là một tên hợp lệ chưa được đặt khác thì nó sẽ bị bỏ qua một cách âm thầm. Nếu một đối số là một tên xấu, nó ghi vào stderr và tạm dừng là phù hợp, mặc dù bất kỳ tên hợp lệ nào trước một dòng không hợp lệ trên dòng lệnh của nó vẫn sẽ được xử lý.

Tôi nghĩ về một cách khác. Tôi thích nó tốt hơn nhiều.

unexport()
        while   unset OPTARG; OPTIND=1           ### always work w/ $1
                case  ${1##[0-9]*}    in         ### same old same old
                (*[!_[:alnum:]]*|"")             ### goodname && $# > 0 || break
                    ${1+"getopts"} : "$1"        ### $# ? getopts : ":"
                    return                       ### getopts errored or ":" didnt
                esac
        do      eval   getopts :s: '"$1" -"${'"$1+s}-\$$1\""
                eval   unset  "$1;  ${OPTARG+$1=\${OPTARG}#-}"
                shift
        done

Vâng, cả hai đều sử dụng rất nhiều kỹ thuật giống nhau. Về cơ bản nếu một var shell không được đặt tham chiếu đến nó sẽ không mở rộng với +việc mở rộng tham số. Nhưng nếu nó được đặt - bất kể giá trị của nó - mở rộng tham số như: ${parameter+word}sẽ mở rộng thành word- và không mở rộng giá trị của biến. Và do đó, các biến shell tự kiểm tra và tự thay thế thành công.

Họ cũng có thể tự thất bại . Trong hàm hàng đầu nếu tìm thấy một tên xấu, tôi chuyển $1sang $2và để $1trống vì điều tiếp theo tôi làm là returnthành công nếu tất cả các đối số đã được xử lý và vòng lặp kết thúc hoặc, nếu đối số không hợp lệ, trình bao sẽ không hợp lệ mở rộng $2vào $1:?đó sẽ giết chết một cái vỏ được viết kịch bản và trả lại một ngắt cho một cái tương tác trong khi viết wordcho thiết bị lỗi chuẩn.

Trong cái thứ hai getoptslàm bài tập. Và nó sẽ không gán một tên xấu - thay vì viết nó sẽ viết ra một thông báo lỗi tiêu chuẩn cho thiết bị lỗi chuẩn. Hơn thế nữa nó tiết kiệm giá trị của arg trong $OPTARG nếu đối số là tên của một biến bộ ở nơi đầu tiên. Vì vậy, sau khi làm getoptstất cả những gì cần thiết là evalmột bộ OPTARG's mở rộng thành nhiệm vụ thích hợp.


2
Một trong những ngày này, tôi sẽ ở trong phòng tâm thần ở đâu đó sau khi cố gắng quấn đầu quanh một trong những câu trả lời của bạn. : D Làm thế nào để câu trả lời khác bị thực thi mã tùy ý? bạn có thể cung cấp một ví dụ?
muru

3
@muru - trừ khi đối số là tên không hợp lệ. nhưng đó không phải là vấn đề - vấn đề là đầu vào không được xác thực. đúng, yêu cầu bạn truyền cho nó một đối số có tên lạ để khiến nó thực thi mã tùy ý - nhưng đó gần như là cơ sở cho mọi CVE trong lịch sử. nếu bạn cố gắng đặt exportmột cái tên lạ, nó sẽ không giết chết máy tính của bạn.
mikeerv

1
@muru - oh, và các đối số có thể tùy ý: var=something; varname=var; export "$varname"là hoàn toàn hợp lệ. điều tương tự cũng xảy ra unset, và với cái này và cái kia, nhưng phút mà nội dung của "$varname"biến đó trở nên điên rồ, điều đó có thể đáng tiếc. và đó là cách mà bashsự thất bại xuất khẩu toàn bộ chức năng đã xảy ra dù sao đi nữa.
mikeerv

1
@mikeserv tôi nghĩ rằng bạn sẽ nhận được rất nhiều upvotes (ít nhất là từ tôi) nếu bạn thay thế mã khó hiểu với mã giải thích riêng của mình (hoặc comment dòng, ít nhất)
PSkocik

1
@PSkocik - xong rồi.
mikeerv
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.