Jak mogę wyeksportować zmienną bez utraty jej wartości?

10

Powiedzmy, że wyeksportowałem zmienną:

foo=bar
export foo

Teraz chciałbym go wyeksportować. To znaczy, jeśli nie, sh -c 'echo "$foo"'nie powinienem bar. foow ogóle nie powinien pojawić się w sh -cśrodowisku. sh -cjest jedynie przykładem, łatwym sposobem na pokazanie obecności zmiennej. Poleceniem może być cokolwiek - może to być coś, na którego zachowanie wpływa po prostu obecność zmiennej w jej otoczeniu.

Mogę:

  1. unset zmienną i zgub ją
  2. Usuń go za pomocą envkażdego polecenia:env -u foo sh -c 'echo "$foo"'
    • niepraktyczne, jeśli chcesz nadal używać bieżącej powłoki.

Idealnie chciałbym zachować wartość zmiennej, ale nie pokazywać jej wcale w procesie potomnym, nawet jako pustej zmiennej.

Chyba mógłbym zrobić:

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

Grozi to tupnięciem otherfoo, jeśli już istnieje.

Czy to jedyny sposób? Czy są jakieś standardowe sposoby?

muru
źródło
1
Możesz powtórzyć wartość w pliku tymczasowym, używając tego,mktemp jeśli jest on wystarczająco przenośny, i rozbroić wartość, a następnie pobrać plik tymczasowy, aby przypisać zmienną. Można utworzyć przynajmniej plik tymczasowy o mniej więcej dowolnej nazwie w przeciwieństwie do zmiennej powłoki.
Thomas Dickey
@Sukminder sh -cPolecenie jest jedynie przykładem. Wykonaj dowolne polecenie, w ramach którego nie możesz rozbroić zmiennej zamiast niej.
muru

Odpowiedzi:

6

Nie ma standardowego sposobu.

Można uniknąć używania zmiennej tymczasowej za pomocą funkcji. Poniższa funkcja dba o to, aby nieuzbrojone zmienne nieuzbrojone i puste zmienne puste. Nie obsługuje jednak funkcji znalezionych w niektórych powłokach, takich jak zmienne tylko do odczytu lub zmienne typowane.

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

W ksh, bash i zsh możesz wyeksportować zmienną za pomocą typeset +x foo. Zachowuje to specjalne właściwości, takie jak typy, więc lepiej go używać. Myślę, że wszystkie powłoki, które mają typesetwbudowane, mają typeset +x.

case $(LC_ALL=C type typeset 2>&1) in
  typeset\ *\ builtin) unexport () { typeset +x -- "$@"; };;
  *) unexport () {  };; # code above
esac
Gilles „SO- przestań być zły”
źródło
1
Dla tych, którzy nie są zaznajomieni ${var+foo}, ocenia, fooczy varjest ustawiony, nawet jeśli pusty, i nic innego.
muru
Powiedz, czy masz jakieś uwagi na temat typeset +xvs export -ndla powłok, które obsługują te pierwsze? Jest export -nrzadszy, czy też nie zachowuje niektórych właściwości?
muru
@muru Jeśli piszesz skrypt bash, możesz używać export -nlub typeset +xobojętnie. W ksh lub zsh jest tylko typeset +x.
Gilles „SO- przestań być zły”
7

EDIT: W przypadku bashtylko, jak wskazano w komentarzach:

-nOpcja exportusuwa exportnieruchomość z każdej podanej nazwie. (Patrz help export.)

Więc dla bash, komenda chcesz to:export -n foo

Dzika karta
źródło
1
Jest to specyficzne dla powłoki (patrz POSIX ), OP nie określił powłoki, ale poprosił o standardowy sposób rozwiązania problemu.
Thomas Dickey,
1
@ThomasDickey, nie był tego świadomy. Dzięki, zaktualizowano.
Wildcard
3

Napisałem podobną funkcję POSIX, ale nie ryzykuje to wykonania dowolnego kodu:

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

Będzie również obsługiwał tyle argumentów, ile chcesz. Jeśli argument jest prawidłową nazwą, która nie jest jeszcze ustawiona, jest dyskretnie ignorowany. Jeśli argument jest złą nazwą, zapisuje go do stderr i zatrzymuje odpowiednio, chociaż każda poprawna nazwa poprzedzająca niepoprawną w wierszu poleceń będzie nadal przetwarzana.

Myślałem o innym sposobie. Bardziej mi się podoba.

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

Oba wykorzystują wiele takich samych technik. Zasadniczo, jeśli zmienna powłoki jest rozbrojona, odniesienie do niej nie będzie rozszerzane wraz z +rozszerzaniem parametrów. Ale jeśli jest ustawiony - niezależnie od jego wartości - rozszerzenie parametru takie jak: ${parameter+word}zostanie rozwinięte do word- a nie do wartości zmiennej. Tak więc zmienne powłoki wykonują autotest i zastępują się sukcesem.

Mogą również zawieść . W górnej funkcji, jeśli zostanie znaleziona zła nazwa, przechodzę $1do $2i pozostawiam $1null, ponieważ następną rzeczą, którą robię, jest albo returnsukces, jeśli wszystkie argumenty zostały przetworzone i pętla jest na końcu, lub, jeśli argument był nieprawidłowy, powłoka będzie rozwiń, $2w $1:?który zabije skryptowaną powłokę i zwróci przerwanie do interaktywnej podczas pisania worddo stderr.

W drugim getoptswykonuje się zadania. I nie przypisuje złej nazwy - raczej napisz, wypisze standardowy komunikat o błędzie do stderr. Co więcej, zapisuje wartość arg, $OPTARG jeśli argument był w pierwszej kolejności nazwą zmiennej ustawionej. Po zrobieniu getoptswszystko, co jest potrzebne, to rozszerzenie evalzestawu OPTARGdo odpowiedniego zadania.

mikeserv
źródło
2
Któregoś dnia będę gdzieś na oddziale psychiatrycznym po próbie owinięcia głowy jedną z twoich odpowiedzi. : D W jaki sposób druga odpowiedź cierpi z powodu wykonania dowolnego kodu? Czy możesz podać przykład?
muru
3
@muru - chyba że argument jest niepoprawną nazwą. ale to nie jest problem - problem polega na tym, że dane wejściowe nie są sprawdzane. tak, konieczne jest przekazanie mu argumentu o dziwnej nazwie, aby mógł wykonać dowolny kod - ale to właściwie podstawa dla każdej CVE w historii. jeśli spróbujesz exportuzyskać dziwne imię, nie zabije twojego komputera.
mikeserv
1
@muru - och, a argumenty mogą być dowolne: var=something; varname=var; export "$varname"jest całkowicie poprawny. to samo dotyczy unset, a wraz z tym i drugim, ale gdy tylko zawartość tej "$varname"zmiennej oszaleje, może być to godne pożałowania. i tak właśnie się bashstało, że i tak nastąpił ten błąd eksportu całej funkcji.
mikeserv
1
@mikeserv Myślę, że zyskałbyś o wiele więcej głosów (przynajmniej ode mnie), gdybyś zastąpił ten zaciemniony kod kodem, który sam się wyjaśnia (lub przynajmniej skomentował linie)
PSkocik
1
@PSkocik - gotowe.
mikeserv