如何取消导出变量而不丢失其值?


10

假设我导出了一个变量:

foo=bar
export foo

现在,我想取消导出它。就是说,如果我这样做,我就sh -c 'echo "$foo"'不会得到barfoo完全不应该出现在sh -c的环境中。 sh -c仅仅是示例,是显示变量存在的一种简单方法。该命令可以是任何东西-它的行为可能仅受其环境中变量的存在影响。

我可以:

  1. unset 变量,并丢失它
  2. 使用env以下命令将其删除:env -u foo sh -c 'echo "$foo"'
    • 如果要继续使用当前的shell一段时间,则不切实际。

理想情况下,我想保留变量的值,但不要让它在子进程中完全显示,甚至不显示为空变量。

我想我可以做到:

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

otherfoo如果已经存在,这有可能会踩踏。

那是唯一的方法吗?有什么标准方法吗?


1
您可以将值回显到临时文件中,mktemp如果可以移植的话可以使用,并取消设置该值,并获取临时文件以分配变量。与shell变量相比,至少可以使用或多或少的任意名称创建一个临时文件。
托马斯·迪基

@Sukminder该sh -c命令仅是示例。如果可以,请采取任何不能代替其设置变量的命令。
muru

Answers:


6

没有标准的方法。

您可以避免通过使用函数来使用临时变量。以下函数注意保持未设置的变量未设置,而空的变量为空。但是,它不支持某些外壳程序中的功能,例如只读或类型化变量。

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

在ksh,bash和zsh中,您可以使用取消导出变量typeset +x foo。这样可以保留诸如类型之类的特殊属性,因此最好使用它。我认为所有具有typeset内置功能的壳都有typeset +x

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

1
对于不熟悉的人${var+foo},它将评估foo是否var设置了(即使为空),否则为空。
muru

说,对于支持前者的shell,您对typeset +xvs 有何评论export -n?是export -n罕见的,或者它不保留一些属性?
muru

@muru如果你正在写一个bash脚本,您可以使用export -ntypeset +x漠然。在ksh或zsh中,只有typeset +x
吉尔(Gilles)'所以

7

编辑:对于bash而已,至于在评论中指出:

从每个给定名称中删除属性的-n选项。(请参阅。)exportexporthelp export

因此,对于bash您想要的命令是:export -n foo


1
那是特定于外壳程序的(请参阅POSIX),OP没有指定外壳程序,而是要求提供解决问题的标准方法。
Thomas Dickey

1
@ThomasDickey,没有意识到这一点。谢谢,更新。
通配符

3

我写了一个类似的POSIX函数,但这不会冒任意代码执行的风险:

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

它还将处理您想要提供的尽可能多的参数。如果参数是没有设置的有效名称,它将被忽略。如果参数是一个错误的名称,则它将写入stderr并适当地暂停,尽管仍会处理命令行上无效名称之前的任何有效名称。

我想到了另一种方式。我好多了。

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

嗯,这两个都使用了很多相同的技术。基本上,如果未设置shell var,则对其的引用将不会通过+参数扩展来扩展。但是,如果将其设置-无论其值如何-像这样的参数扩展:${parameter+word}都会扩展为word-而不是变量的值。因此,shell变量在成功时会进行自我测试和自我替代。

他们也可以自我失败。在top函数中,如果发现了一个不好的名字,我$1进入$2并保留为$1null,因为return如果所有的args已被处理且循环结束,那么我接下来要做的就是成功,或者如果arg无效,则shell将执行扩展$2$1:?其中将杀死脚本化的shell,并在写入wordstderr 时将中断返回给交互式中断。

在第二个getopts中,分配作业。而且它不会分配一个错误的名称-而是写它会向stderr写出标准错误消息。此外,$OPTARG 如果参数首先是set变量的名称,它将保存arg的值。因此,在完成getopts所有之后,需要将eval集合OPTARG扩展为适当的分配。


2
这些日子之一,我将把我的头缠在您的答案之一后,将来到精神病房。:D其他答案怎么会遭受任意代码执行?你能举个例子吗?
muru

3
@muru-除非参数为无效名称,否则不会。但这不是问题-问题是输入未经验证。是的,要求您传递一个带有古怪名称的参数以使其执行任意代码-但这几乎是历史上每个CVE的基础。如果您尝试使用export一个奇怪的名称,它不会杀死您的计算机。
mikeserv '16

1
@muru-哦,参数可以是任意的:var=something; varname=var; export "$varname"完全有效。这同样适用unset,以及与此,并与其他,但是这微小的内容"$varname"变量都疯了,也可能是令人遗憾的。这就是整个bash功能导出崩溃的方式。
mikeserv '16

1
@mikeserv我认为,如果您用解释自己的代码(或至少注释了这些行)替换那些混淆的代码,至少会收到更多的赞扬(至少来自我)
PSkocik

1
@PSkocik-完成。
mikeserv '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.