【问题标题】:Bash: Bullet-proof one-liners for managing PATH-like variablesBash:用于管理类似 PATH 的变量的防弹单行程序
【发布时间】:2020-04-13 14:38:40
【问题描述】:

我正在寻找四个简单、防弹、尽可能短的 Bash 函数:

PrependPath name path [path ...]
AppendPath name path [path ...]
RemovePath name path [path ...]
UniquePath name

分别是:

  • 将路径添加到name 指定的 Bash 变量中,如果这些路径尚未包含在该变量中的某个位置,
  • 将路径附加到由name 指定的 Bash 变量,如果这些路径尚未包含在该变量的某个位置,
  • name 指定的 Bash 变量中删除给定路径的所有实例,
  • 过滤掉变量中除第一次(最左边)出现的所有重复项之外的所有重复项。

例如(我没有忘记任何$ 标志):

export PATH=/bin:/usr/bin:/bin
PrependPath PATH /usr/local/bin  # --> PATH=/usr/local/bin:/bin:/usr/bin:/bin
AppendPath PATH /usr/local/bin   # --> PATH=/usr/local/bin:/bin:/usr/bin:/bin  (nothing changed, as was already there from PrependPath)
RemovePath PATH /usr/local/bin   # --> PATH=/bin:/usr/bin:/bin
UniquePath PATH                  # --> PATH=/bin:/usr/bin

一旦我们有了这些函数,我们就可以将它们用于遵循与PATH 变量相同(或非常相似的规则)的任何路径变量,例如LD_LIBRARY_PATHLIBRARY_PATHINFOPATH、.. .

难点在于我希望这些函数适用于绝对所有PATH 中可能出现的合法路径,这基本上消除了字符\0/ 和@ 987654343@,但允许所有其他字符(假设它是有效编码的 UTF-8),包括特定的空格和换行符。

我想保留关于“没有一个头脑正常的人会使用这样的路径”的哲学讨论。如果我可以合法地创建这样的路径,并且将其放在PATH 中是合法的,那么这是公平的游戏。

还有一些注意事项:

  • 除了导出/更改指定的路径变量之外,函数不应以任何方式修改父环境(例如向其添加函数/变量)。
  • 该函数不应将 /usr//usr 之类的路径等同起来,它们之间的区别仅在于斜线。
  • 该函数应该正确处理空路径(指定当前目录),如果它们不存在,则不会意外地将它们添加到路径变量中,如果它们已经 ,则不会意外删除它们在那里。
  • 即使PATH,该函数也应该可以工作。不能排除这种情况是不可能的。

我创建了一个单元测试脚本,它体现了我认为对四个函数的行为完全合理和常识性的期望(我想避免外部链接):

function PrependPath() { TODO }
function AppendPath() { TODO }
function RemovePath() { TODO }
function UniquePath() { TODO }

function Assert() { ((i++)); [[ "$TESTPATH" == "$1" ]] && echo "$i) SUCCESS" || echo "$i) FAILURE => Got >$TESTPATH<"; }

i=0
echo "Test: PrependPath"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /usr/sbin
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /bin
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /usr/slash
Assert "/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /new
Assert "/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /new
Assert "/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH ""
Assert ":/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH ""
Assert ":/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH "/usr"
Assert "/usr::/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"

TESTPATH=
PrependPath TESTPATH /multiple /new
Assert "/new:/multiple"
PrependPath TESTPATH /and /new /foo /and
Assert "/foo:/and:/new:/multiple"

TESTPATH=":/foo:/bar:"
PrependPath TESTPATH /bar
Assert ":/foo:/bar:"

TESTPATH="/foo:/bar:"
PrependPath TESTPATH ""
Assert "/foo:/bar:"

TESTPATH=":/foo:/bar"
PrependPath TESTPATH ""
Assert ":/foo:/bar"

TESTPATH="/foo::/bar"
PrependPath TESTPATH ""
Assert "/foo::/bar"

TESTPATH=$'/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'
PrependPath TESTPATH /ano
Assert $'/ano:/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'
PrependPath TESTPATH $'/te st\nnew/foo'
Assert $'/ano:/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'
PrependPath TESTPATH $'/ne w\ner\n'
Assert $'/ne w\ner\n:/ano:/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'

TESTPATH="/foo:/bar"
PrependPath TESTPATH "/b.r" "/fo+" "/fo*" "/b[a]r" "\foo" "/food?"
Assert "/food?:\foo:/b[a]r:/fo*:/fo+:/b.r:/foo:/bar"

TESTPATH=$':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:'
PrependPath TESTPATH "/a[d" "/mor(e" "/other{" "/ano"
Assert $'/ano:/other{::/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:'

TESTPATH=$'\new'
PrependPath TESTPATH "\new"
Assert $'\\new:\new'

TESTPATH=
PrependPath TESTPATH "/foo"
Assert "/foo"
PrependPath TESTPATH ""
Assert ":/foo"

TESTPATH=$'\n:/foo:/bar:\n'
PrependPath TESTPATH "/bar"
Assert $'\n:/foo:/bar:\n'
PrependPath TESTPATH $'\n'
Assert $'\n:/foo:/bar:\n'
PrependPath TESTPATH "/new"
Assert $'/new:\n:/foo:/bar:\n'
PrependPath TESTPATH ""
Assert $':/new:\n:/foo:/bar:\n'

TESTPATH=$':/foo:/bar:\n\n'
PrependPath TESTPATH "/bar"
Assert $':/foo:/bar:\n\n'
PrependPath TESTPATH $'\n'
Assert $'\n::/foo:/bar:\n\n'
PrependPath TESTPATH $'\n\n'
Assert $'\n::/foo:/bar:\n\n'
PrependPath TESTPATH "/new"
Assert $'/new:\n::/foo:/bar:\n\n'

TESTPATH=
PrependPath TESTPATH ""
Assert ":"
PrependPath TESTPATH ""
Assert ":"
PrependPath TESTPATH /new
Assert "/new:"
PrependPath TESTPATH ""
Assert "/new:"

TESTPATH=":/bin:"
PrependPath TESTPATH ""
Assert ":/bin:"
PrependPath TESTPATH "/foo"
Assert "/foo::/bin:"

TESTPATH="::"
PrependPath TESTPATH ""
Assert "::"
PrependPath TESTPATH "/foo"
Assert "/foo:::"

TESTPATH=":::"
PrependPath TESTPATH ""
Assert ":::"
PrependPath TESTPATH "/foo"
Assert "/foo::::"

ORIGPATH="$PATH"
PATH=
TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /usr/sbin /bin /usr/slash /new /foo /new
Assert "/foo:/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PATH="$ORIGPATH"

i=0
echo
echo "Test: AppendPath"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
AppendPath TESTPATH /usr/sbin
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
AppendPath TESTPATH /bin
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
AppendPath TESTPATH /usr/slash
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash"
AppendPath TESTPATH /new
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new"
AppendPath TESTPATH /new
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new"
AppendPath TESTPATH ""
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new:"
AppendPath TESTPATH ""
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new:"
AppendPath TESTPATH "/usr"
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new::/usr"

TESTPATH=
AppendPath TESTPATH /multiple /new
Assert "/multiple:/new"
AppendPath TESTPATH /and /new /foo /and
Assert "/multiple:/new:/and:/foo"

TESTPATH=":/foo:/bar:"
AppendPath TESTPATH /bar
Assert ":/foo:/bar:"

TESTPATH="/foo:/bar:"
AppendPath TESTPATH ""
Assert "/foo:/bar:"

TESTPATH=":/foo:/bar"
AppendPath TESTPATH ""
Assert ":/foo:/bar"

TESTPATH="/foo::/bar"
AppendPath TESTPATH ""
Assert "/foo::/bar"

TESTPATH=$'/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'
AppendPath TESTPATH /ano
Assert $'/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo::/ano'
AppendPath TESTPATH $'/te st\nnew/foo'
Assert $'/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo::/ano'
AppendPath TESTPATH $'/ne w\ner\n'
Assert $'/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo::/ano:/ne w\ner\n'

TESTPATH="/foo:/bar"
AppendPath TESTPATH "/b.r" "/fo+" "/fo*" "/b[a]r" "/bar" "\foo" "/food?"
Assert "/foo:/bar:/b.r:/fo+:/fo*:/b[a]r:\foo:/food?"

TESTPATH=$':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:'
AppendPath TESTPATH "/a[d" "/mor(e" "/other{" "/ano"
Assert $':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e::/other{:/ano'

TESTPATH=$'\new'
AppendPath TESTPATH "\new"
Assert $'\new:\\new'

TESTPATH=
AppendPath TESTPATH "/foo"
Assert "/foo"
AppendPath TESTPATH ""
Assert "/foo:"

TESTPATH=$'\n:/foo:/bar:\n'
AppendPath TESTPATH "/bar"
Assert $'\n:/foo:/bar:\n'
AppendPath TESTPATH $'\n'
Assert $'\n:/foo:/bar:\n'
AppendPath TESTPATH "/new"
Assert $'\n:/foo:/bar:\n:/new'
AppendPath TESTPATH ""
Assert $'\n:/foo:/bar:\n:/new:'

TESTPATH=$':/foo:/bar:\n\n'
AppendPath TESTPATH "/bar"
Assert $':/foo:/bar:\n\n'
AppendPath TESTPATH $'\n'
Assert $':/foo:/bar:\n\n:\n'
AppendPath TESTPATH $'\n\n'
Assert $':/foo:/bar:\n\n:\n'
AppendPath TESTPATH "/new"
Assert $':/foo:/bar:\n\n:\n:/new'

TESTPATH=
AppendPath TESTPATH ""
Assert ":"
AppendPath TESTPATH ""
Assert ":"
AppendPath TESTPATH /new
Assert ":/new"
AppendPath TESTPATH ""
Assert ":/new"

TESTPATH=":/bin:"
AppendPath TESTPATH ""
Assert ":/bin:"
AppendPath TESTPATH "/foo"
Assert ":/bin::/foo"

TESTPATH="::"
AppendPath TESTPATH ""
Assert "::"
AppendPath TESTPATH "/foo"
Assert ":::/foo"

TESTPATH=":::"
AppendPath TESTPATH ""
Assert ":::"
AppendPath TESTPATH "/foo"
Assert "::::/foo"

ORIGPATH="$PATH"
PATH=
TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
AppendPath TESTPATH /usr/sbin /bin /usr/slash /new /foo /new
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new:/foo"
PATH="$ORIGPATH"

i=0
echo
echo "Test: RemovePath"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/"
RemovePath TESTPATH /usr/sbin
Assert "/usr/bin:/bin:/usr/sbin/tmp:/usr/sbin/"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/:/foo"
RemovePath TESTPATH /usr/bin /usr/sbin /foo
Assert "/bin:/usr/sbin/tmp:/usr/sbin/"

TESTPATH=$':/te st\nnew/foo:/and:/ano\nth er:/more:'
RemovePath TESTPATH /and
Assert $':/te st\nnew/foo:/ano\nth er:/more:'

TESTPATH=$':/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'
RemovePath TESTPATH $'/te st\nnew/foo'
Assert $':/and:/ano\nth er:/more:'

TESTPATH=$':/te st\nnew/foo:/and:/ano\nth er:/more:'
RemovePath TESTPATH ""
Assert $'/te st\nnew/foo:/and:/ano\nth er:/more'

TESTPATH=$':/te st\nnew/foo:/and:/ano\nth er:/more:'
RemovePath TESTPATH "/a.d" "\and" "/andy?" $'/te st\nnew/fo+' "/an*"
Assert $':/te st\nnew/foo:/and:/ano\nth er:/more:'

TESTPATH=$':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:'
RemovePath TESTPATH "/a[d" "/mor(e" "/other{" "/ano"
Assert $':/te[ st\nnew/foo:/ano\nth er:'

TESTPATH=$':/te st\nnew/foo:/and:/ano\nth er:\n:/more:'
RemovePath TESTPATH "/sp ace" $'/new\nline' $'\n' "" $'/ano\nth er'
Assert $'/te st\nnew/foo:/and:/more'

TESTPATH="\no:\newline"
RemovePath TESTPATH $'\no'
Assert "\no:\newline"
RemovePath TESTPATH "\no"
Assert "\newline"

TESTPATH=$'\n:/more:\n\n:/of:\n:/th is:\n\n:\n'
RemovePath TESTPATH ""
Assert $'\n:/more:\n\n:/of:\n:/th is:\n\n:\n'
RemovePath TESTPATH "\n"
Assert $'\n:/more:\n\n:/of:\n:/th is:\n\n:\n'
RemovePath TESTPATH $'\n\n'
Assert $'\n:/more:/of:\n:/th is:\n'
RemovePath TESTPATH $'\n'
Assert $'/more:/of:/th is'

TESTPATH=$'\n:/more:\n\n:/of:\n:/th is:\n\n:\n'
RemovePath TESTPATH $'\n'
Assert $'/more:\n\n:/of:/th is:\n\n'
RemovePath TESTPATH /of
Assert $'/more:\n\n:/th is:\n\n'

TESTPATH=$'\n:/more:\n\n:/of:\n:/th is::\n'
RemovePath TESTPATH $'\n'
Assert $'/more:\n\n:/of:/th is:'

TESTPATH=$'\n:/more:\n\n:/of:\n:/th is::\n'
RemovePath TESTPATH ""
Assert $'\n:/more:\n\n:/of:\n:/th is:\n'

TESTPATH=":::"
RemovePath TESTPATH "/foo"
Assert ":::"
RemovePath TESTPATH ""
Assert ""

TESTPATH="::"
RemovePath TESTPATH "/foo"
Assert "::"
RemovePath TESTPATH ""
Assert ""

TESTPATH=":"
RemovePath TESTPATH "/foo"
Assert ":"
RemovePath TESTPATH ""
Assert ""

TESTPATH="::/foo"
RemovePath TESTPATH "/foo"
Assert ":"

TESTPATH="::/foo"
RemovePath TESTPATH ""
Assert "/foo"

TESTPATH=":/foo"
RemovePath TESTPATH "/foo"
Assert ":"

TESTPATH=":/foo"
RemovePath TESTPATH ""
Assert "/foo"
RemovePath TESTPATH "/foo"
Assert ""

TESTPATH="/foo::"
RemovePath TESTPATH "/foo"
Assert ":"

TESTPATH="/foo::"
RemovePath TESTPATH ""
Assert "/foo"

TESTPATH="/foo:"
RemovePath TESTPATH "/foo"
Assert ":"

TESTPATH="/foo:"
RemovePath TESTPATH ""
Assert "/foo"

TESTPATH=""
RemovePath TESTPATH ""
Assert ""
RemovePath TESTPATH "/foo"
Assert ""

ORIGPATH="$PATH"
PATH=
TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/"
RemovePath TESTPATH /usr/sbin /bin /foo
Assert "/usr/bin:/usr/sbin/tmp:/usr/sbin/"
PATH="$ORIGPATH"

i=0
echo
echo "Test: UniquePath"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/"
UniquePath TESTPATH
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin/tmp:/usr/sbin/"
UniquePath TESTPATH
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin/tmp:/usr/sbin/"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/:/usr/sbin:/bin"
UniquePath TESTPATH
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin/tmp:/usr/sbin/"

TESTPATH=":/foo:/bar:/foo"
UniquePath TESTPATH
Assert ":/foo:/bar"

TESTPATH="/foo:/bar:/foo:"
UniquePath TESTPATH
Assert "/foo:/bar:"

TESTPATH=":/foo:/bar:/foo:"
UniquePath TESTPATH
Assert ":/foo:/bar"

TESTPATH="/foo:/bar::/foo"
UniquePath TESTPATH
Assert "/foo:/bar:"

TESTPATH="/foo:/bar::/foo:"
UniquePath TESTPATH
Assert "/foo:/bar:"

TESTPATH=$':/te st\nnew/foo:/ano:/ano\nth er:th er::/more:/te st\nnew/foo:'
UniquePath TESTPATH
Assert $':/te st\nnew/foo:/ano:/ano\nth er:th er:/more'

TESTPATH=$'/te st\nnew/foo:/and:/ano\nth er:/more:/a.d:\\and:/andy?:/te st\nnew/fo+:/an*:/more'
UniquePath TESTPATH
Assert $'/te st\nnew/foo:/and:/ano\nth er:/more:/a.d:\\and:/andy?:/te st\nnew/fo+:/an*'

TESTPATH=$':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:/mor(e:/other{:/ano:/a[d'
UniquePath TESTPATH
Assert $':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:/other{:/ano'

TESTPATH=$'\\no:\newline:\no:\\newline:\no'
UniquePath TESTPATH
Assert $'\\no:\newline:\no:\\newline'

TESTPATH="/foo:/foo"
UniquePath TESTPATH
Assert "/foo"
UniquePath TESTPATH
Assert "/foo"

TESTPATH=":"
UniquePath TESTPATH
Assert ":"

TESTPATH="::"
UniquePath TESTPATH
Assert ":"

TESTPATH=":::"
UniquePath TESTPATH
Assert ":"

TESTPATH=$'\n:/more::/of:\n:/th is:\n\n:\n'
UniquePath TESTPATH
Assert $'\n:/more::/of:/th is:\n\n'

TESTPATH=$':/more:/of::\n\n::/th is:\n\n:\n'
UniquePath TESTPATH
Assert $':/more:/of:\n\n:/th is:\n'

TESTPATH=$'/foo:\n:/foo'
UniquePath TESTPATH
Assert $'/foo:\n'

TESTPATH=$'/foo::'
UniquePath TESTPATH
Assert $'/foo:'

TESTPATH=
UniquePath TESTPATH
Assert ""

ORIGPATH="$PATH"
PATH=
TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/:/bin"
UniquePath TESTPATH
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin/tmp:/usr/sbin/"
PATH="$ORIGPATH"

如果您的函数通过了所有这些测试,并且没有其他人可以找到我没有在脚本中检查过的缺陷,那么您已经完全回答了我的问题。

当然,我在 Stack Overflow 上尝试过的任何其他相关问题的答案都没有为我提供解决此问题的方法(123456789)。也许我错过了一个,但是当我在我指定的通用级别上寻找所有四个函数时,这仍然不能完全回答我的问题。

我尝试过的

使用字符串修改变量的规范很容易使用间接扩展来处理,例如如果$1PATH 那么${!1}$PATH。可以使用export "${1}"=XXX 间接设置/导出变量。在最终解决方案中,可能有更有效/更优雅的方式来处理多个输入,但作为备用解决方案,您始终可以在它们上使用 for 循环,因此可以将任务简化为只需要四个函数来执行所需的使用单个参数对 PATH 变量进行操作。

不污染父命名空间可以使用local 关键字来实现变量,但子函数最多只能是unset -f,这并不能真正满足不可能改变父环境的要求。附加到 PATH 变量可以使用替代参数扩展巧妙地完成,即类似 PATH="${PATH:+${PATH}:}/new/path" 的东西。前置类似于PATH="/new/path${PATH:+:${PATH}}"

这些表达式正确地处理添加或不添加冒号分隔符几乎一直,并且对于它们没有简单条件的特殊情况,可能可以添加适当的修复它。有了所有这些构建块可供我使用,我的问题的关键是如何以防弹的方式稳健地分割路径,以便能够检查某个路径是否已经存在。我的尝试之一就是在不提取组件的情况下搜索字符串:

function AppendPath() { echo "$PATH" | grep -Eq "(^|:)$1($|:)" || export PATH="$1${PATH:+:${PATH}}"; }

但这失败了,因为$1 的内容被解释为正则表达式。此外,当 PATH 为空时,对 grep 的调用会失败,这意味着需要像 /bin/grep 这样的绝对路径,但这感觉不是特别便携。对于RemovePath,我尝试使用空字符作为分隔符并执行反向 grep,例如类似:

function RemovePath() { __path="$(echo -n "$PATH" | tr ':' '\0' | grep -zxvF "$1" | tr '\0' ':')"; export PATH="${__path%:}"; }

我在grep 上使用了-F 以确保它不会被视为正则表达式,将整行与-x 匹配,将空分隔符与-z 一起使用,并与-v 反转匹配。除了某些极端情况和空 PATH 情况的明显问题之外,grep -zF 仍然在每个换行符处划分搜索模式,尽管使用了空分隔符,这意味着尝试删除 $'/foo\nbar' 将改为只删除 @ 的所有实例987654374@ 和bar。命令替换$() 的一个偷偷摸摸的问题在这里也浮出水面,它会截断所有尾随的换行符。试试:

echo -n "$(echo -n $'Hey\nthere\n\n\n')"

我尝试使用IFS=:PATH 分解为一个数组,但除此之外,命令替换是我在尝试使用"$(IFS=:; echo -n "${pathparts[*]}")" 之类的东西重新组装它时再次卡住的地方。现在我正在研究一个基于read 命令和手动循环的bash 内置想法,以避免命令替换、grep 和空PATH 问题。我实际上是想避免用很多不起作用的东西来污染我的问题,但它被要求了,我希望至少现在人们相信我在发布之前已经认真对待了这个问题。

【问题讨论】:

  • 我需要在来自我管理的许多不同用户配置文件的许多机器上使用此功能,因此需要将这些功能放在许多 bashrc 和其他必需的 自包含 脚本中也不能假设他们从哪个用户那里运行等等。因此我不在乎它有多少个字符或类似的东西。任何简短的东西都可以。我只想排除带有 3 个子功能等的 1 页答案等...
  • 这只是一个很长的请求,请某人为您编写四个函数。坦率地说,您在编写测试用例上花费的时间可能比使用这四个函数所节省的时间要多,而不是根据需要手动更新路径。
  • 你说你没有忘记任何$ 标志,但调用你的函数的方式可能不是最好的——你需要在你的函数中使用namerefs、间接,甚至eval .我认为,如果您缩小范围(例如,仅询问是否向PATH 添加内容),您可能会得到更好的回应,展示您的尝试以及它是如何失败的。
  • 我建议你花一些时间编写你自己的函数来满足你的所有要求。我个人有一个path(),它支持插入、删除、唯一、列表。它还支持指定要操作的 VAR(默认为 PATH)。
  • 如果TESTPATH 为空并且您希望将当前目录添加到其中,即添加'',那么尊重该愿望的唯一正确输出TESTPATH:...

标签: bash function path duplicates add


【解决方案1】:

在我的测试范围内,这是一个解决方案,在管理PATH-like 变量的各种情况下总是会做正确的事情:

# Example: PrependPath PATH /usr/local/cuda/bin
function PrependPath() {
    local __args __item
    for __args in "${@:2}"; do
        [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do
            [[ "$__item" == "$__args" ]] && continue 2
        done <<< "${!1}:"
        ([[ -n "${!1#:}" ]] || [[ -z "$__args" ]]) && __args+=':'
        export "${1}"="$__args${!1}"
    done
}
# Example: AppendPath PATH /usr/local/cuda/bin
function AppendPath() {
    local __args __item
    for __args in "${@:2}"; do
        [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do
            [[ "$__item" == "$__args" ]] && continue 2
        done <<< "${!1}:"
        ([[ -n "${!1#:}" ]] || [[ -z "$__args" ]]) && __args=":$__args"
        export "${1}"="${!1}$__args"
    done
}
# Example: RemovePath INFOPATH /usr/local/texlive/2019/texmf-dist/doc/info
function RemovePath() {
    local __item __args __path=
    [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do
        for __args in "${@:2}"; do
            [[ "$__item" == "$__args" ]] && continue 2
        done
        __path="$__path:$__item"
    done <<< "${!1}:"
    [[ "$__path" != ":" ]] && __path="${__path#:}"
    export "${1}"="$__path"
}
# Example: UniquePath LD_LIBRARY_PATH
function UniquePath() {
    local __item __args __path= __seen=()
    [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do
        for __args in "${__seen[@]}"; do
            [[ "$__item" == "$__args" ]] && continue 2
        done
        __seen+=("$__item")
        __path="$__path:$__item"
    done <<< "${!1}:"
    [[ "$__path" != ":" ]] && __path="${__path#:}"
    export "${1}"="$__path"
}

解决问题症结的方法是使用read:作为分隔符,并且只使用bash循环检查/组装所需的输出路径,无需命令替换,grepawk , sed 或任何其他外部命令。

汇总成“单行”,以便它们可以轻松地在脚本、命令行和.bashrc 中使用:

# Functions to manage arbitrary PATH-like variables
function PrependPath() { local __args __item; for __args in "${@:2}"; do [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do [[ "$__item" == "$__args" ]] && continue 2; done <<< "${!1}:"; ([[ -n "${!1#:}" ]] || [[ -z "$__args" ]]) && __args+=':'; export "${1}"="$__args${!1}"; done; }
function AppendPath() { local __args __item; for __args in "${@:2}"; do [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do [[ "$__item" == "$__args" ]] && continue 2; done <<< "${!1}:"; ([[ -n "${!1#:}" ]] || [[ -z "$__args" ]]) && __args=":$__args"; export "${1}"="${!1}$__args"; done; }
function RemovePath() { local __item __args __path=; [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do for __args in "${@:2}"; do [[ "$__item" == "$__args" ]] && continue 2; done; __path="$__path:$__item"; done <<< "${!1}:"; [[ "$__path" != ":" ]] && __path="${__path#:}"; export "${1}"="$__path"; }
function UniquePath() { local __item __args __path= __seen=(); [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do for __args in "${__seen[@]}"; do [[ "$__item" == "$__args" ]] && continue 2; done; __seen+=("$__item"); __path="$__path:$__item"; done <<< "${!1}:"; [[ "$__path" != ":" ]] && __path="${__path#:}"; export "${1}"="$__path"; }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-29
    • 1970-01-01
    • 2014-08-20
    • 1970-01-01
    • 2011-11-07
    • 1970-01-01
    相关资源
    最近更新 更多