Bagaimana saya bisa membatalkan ekspor variabel, tanpa kehilangan nilainya?

10

Katakanlah saya mengekspor variabel:

foo=bar
export foo

Sekarang, saya ingin membatalkan ekspornya. Itu untuk mengatakan, jika saya lakukan sh -c 'echo "$foo"'saya tidak seharusnya bar. fooseharusnya tidak muncul di sh -clingkungan sama sekali. sh -chanyalah sebuah contoh, cara mudah untuk menunjukkan keberadaan suatu variabel. Perintah itu bisa berupa apa saja - itu mungkin sesuatu yang perilakunya dipengaruhi hanya oleh kehadiran variabel di lingkungannya.

Saya bisa:

  1. unset variabel, dan kehilangan itu
  2. Hapus menggunakan envuntuk setiap perintah:env -u foo sh -c 'echo "$foo"'
    • tidak praktis jika Anda ingin terus menggunakan shell saat ini untuk sementara waktu.

Idealnya, saya ingin menyimpan nilai variabel, tetapi tidak memilikinya muncul sama sekali dalam proses anak, bahkan sebagai variabel kosong.

Saya kira saya bisa melakukan:

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

Ini berisiko menginjak otherfoo, jika sudah ada.

Apakah itu satu-satunya jalan? Apakah ada cara standar?

muru
sumber
1
Anda bisa menggemakan nilai menjadi file sementara, menggunakan mktempjika itu cukup portabel, dan menghapus nilai, dan sumber file sementara untuk menetapkan variabel. Setidaknya file sementara dapat dibuat dengan nama yang kurang lebih berbeda dengan variabel shell.
Thomas Dickey
@Sukminder sh -cPerintah ini hanyalah sebuah contoh. Ambil perintah apa pun di mana Anda tidak dapat menghapus variabel sebagai gantinya, jika Anda mau.
muru

Jawaban:

6

Tidak ada cara standar.

Anda dapat menghindari menggunakan variabel sementara dengan menggunakan fungsi. Fungsi berikut berhati-hati untuk menjaga variabel yang tidak disetel tidak disetel dan mengosongkan variabel. Namun tidak mendukung fitur yang ditemukan di beberapa shell seperti read-only atau variabel yang diketik.

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

Di ksh, bash, dan zsh, Anda dapat menghapus variabel dengan typeset +x foo. Ini mempertahankan properti khusus seperti tipe, jadi lebih baik menggunakannya. Saya pikir semua shell yang memiliki typesetbuiltin miliki typeset +x.

case $(LC_ALL=C type typeset 2>&1) in
  typeset\ *\ builtin) unexport () { typeset +x -- "$@"; };;
  *) unexport () {  };; # code above
esac
Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
Bagi mereka yang tidak terbiasa dengan ${var+foo}, itu mengevaluasi fooapakah vardiatur, bahkan jika kosong, dan tidak ada yang sebaliknya.
muru
Katakan, apakah Anda memiliki komentar tentang typeset +xvs export -nuntuk cangkang yang mendukung yang sebelumnya? Apakah export -nlebih jarang, atau tidak mempertahankan beberapa properti?
muru
@muru Jika Anda menulis skrip bash, Anda dapat menggunakan export -natau typeset +xacuh tak acuh. Di ksh atau zsh, hanya ada typeset +x.
Gilles 'SANGAT berhenti menjadi jahat'
7

EDIT: Hanya untuk bash, seperti yang ditunjukkan dalam komentar:

The -npilihan untuk exportmenghapus exportproperti dari masing-masing nama yang diberikan. (Lihat help export.)

Jadi untuk bash, perintah yang Anda inginkan adalah:export -n foo

Wildcard
sumber
1
Itu khusus shell (lihat POSIX ), OP tidak menentukan shell, tetapi meminta cara standar untuk memecahkan masalah.
Thomas Dickey
1
@ThomasDickey, tidak menyadarinya. Terima kasih, diperbarui.
Wildcard
3

Saya menulis fungsi POSIX serupa, tetapi ini tidak berisiko eksekusi kode arbitrer:

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

Ini juga akan menangani argumen sebanyak yang Anda mau berikan. Jika argumen adalah nama yang valid yang tidak diatur sebelumnya, maka diam-diam diabaikan. Jika argumen adalah nama yang buruk ia menulis ke stderr dan berhenti sesuai, meskipun nama yang valid sebelum yang tidak sah pada baris perintah masih akan diproses.

Saya memikirkan cara lain. Saya sangat menyukainya.

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

Yah, keduanya menggunakan banyak teknik yang sama. Pada dasarnya jika var shell tidak disetel referensi untuk itu tidak akan diperluas dengan +ekspansi parameter. Tetapi jika diatur - terlepas dari nilainya - ekspansi parameter seperti: ${parameter+word}akan diperluas ke word- dan tidak ke nilai variabel. Dan begitu juga variabel swa-uji dan swa-pengganti pada kesuksesan.

Mereka juga bisa gagal sendiri . Dalam fungsi teratas jika ditemukan nama buruk, saya pindah $1ke $2dan meninggalkan $1nol karena hal berikutnya yang saya lakukan adalah returnberhasil jika semua arg telah diproses dan loop di akhir, atau, jika arg tidak valid, shell akan memperluas $2ke $1:?mana akan membunuh shell scripted dan mengembalikan interupsi ke yang interaktif saat menulis wordke stderr.

Yang kedua getoptsmengerjakan tugas. Dan itu tidak akan menetapkan nama yang buruk - melainkan menulisnya akan menulis pesan kesalahan standar ke stderr. Terlebih lagi itu menyimpan nilai argumen $OPTARG jika argumen adalah nama variabel set di tempat pertama. Jadi setelah melakukan getoptssemua yang diperlukan adalah ekspansi evalset OPTARGke penugasan yang sesuai.

mikeserv
sumber
2
Suatu hari, saya akan berada di bangsal psikiatris di suatu tempat setelah mencoba untuk membungkus kepala saya di sekitar salah satu jawaban Anda. : D Bagaimana jawaban lain menderita karena eksekusi kode arbitrer? Bisakah Anda memberikan contoh?
muru
3
@muru - tidak kecuali argumen adalah nama yang tidak valid. tapi itu bukan masalah - masalahnya adalah input tidak divalidasi. ya, Anda harus menyampaikan argumen dengan nama aneh untuk membuatnya mengeksekusi kode arbitrer - tapi itu cukup banyak dasar untuk setiap CVE dalam sejarah. jika Anda mencoba exportnama yang aneh, itu tidak akan mematikan komputer Anda.
mikeserv
1
@muru - oh, dan argumennya bisa arbitrer: var=something; varname=var; export "$varname"sangat valid. hal yang sama berlaku untuk unset, dan dengan ini dan dengan yang lain, tetapi begitu isi "$varname"variabel itu menjadi gila, itu bisa disesalkan. dan begitulah keseluruhan bashkegagalan ekspor fungsi itu terjadi.
mikeserv
1
@mikeserv saya pikir Anda akan mendapatkan lebih banyak upvotes (setidaknya dari saya) jika Anda mengganti kode yang dikaburkan dengan kode yang menjelaskan sendiri (atau mengomentari baris, setidaknya)
PSkocik
1
@PSkocik - selesai.
mikeserv