是否有一般方法在bash中添加/添加/删除一般环境变量的路径?

时间:2014-07-01 16:40:04

标签: bash

我想知道是否有人有好方法在bash中添加/删除环境变量中的路径(而不仅仅是PATH变量)。

我的情况是我在集群上实现了一个类似于模块的简单系统,我没有管理员权限。我希望能够编写如下脚本:

openmpi_module.sh

#!/bin/bash

if [ $1 == "load" ]; then
  path_prepend PATH "$HOME/apps/openmpi-1.8.1-gcc-4.9.0/bin"
  path_prepend CPATH "$HOME/apps/openmpi-1.8.1-gcc-4.9.0/include"
  ...
fi

if [ $1 == "unload" ]; then
  path_remove PATH "$HOME/apps/openmpi-1.8.1-gcc-4.9.0/bin"
  path_remove CPATH "$HOME/apps/openmpi-1.8.1-gcc-4.9.0/include"
  ...
fi

我发现questions dealing具体修改PATH变量,但不是一般环境变量。看起来这些解决方案的一般版本将是一个有用的工具。

我目前的解决方案是在我的.bash_profile中提供以下内容,但我想知道是否有人有更优雅的解决方案(没有安装实际Modules system的本地副本)。对于使用这么多evalecho s我感到很不舒服,这是我宁愿避免的做法。

#!/bin/bash
# modified from https://stackoverflow.com/questions/370047/

function path_remove () {
eval export $(echo $1=\$\(echo -n $(echo -n "\$$1 | awk -v RS=: -v ORS=: '\$0 != \"'$2'\"' | sed 's/:\$//'")\))
}

function path_append () {
path_remove $1 $2
eval export $1="\$$1:$2"
}

function path_prepend () {
path_remove $1 $2
eval export $1="$2:\$$1"
}

3 个答案:

答案 0 :(得分:3)

以下是紧凑的bash函数,其中:

  • 使用 eval来间接设置变量,以避免printf -v,如@Charles Duffy's excellent answer中那样。
  • 仅使用参数扩展来操作路径列表
  • 自包含(它们不依赖于彼此 - 以牺牲复制某些代码为代价)。
  • 强制预先/附加,与OP的功能一样:即,如果指定的路径条目已存在但位于不同的位置,则强制进入所需位置

警告:现有的重复条目只有在不直接相邻时才会被删除(请参阅下面的替代方案)。

#!/usr/bin/env bash

# The functions below operate on PATH-like variables whose fields are separated
# with ':'.
# Note: The *name* of the PATH-style variable must be passed in as the 1st
#       argument and that variable's value is modified *directly*.

# SYNOPSIS: path_prepend varName path
# Note: Forces path into the first position, if already present.
#       Duplicates are removed too, unless they're directly adjacent.
# EXAMPLE: path_prepend PATH /usr/local/bin
path_prepend() {
  local aux=":${!1}:"
  aux=${aux//:$2:/:}; aux=${aux#:}; aux=${aux%:}
  printf -v "$1" '%s' "${2}${aux:+:}${aux}"  
}

# SYNOPSIS: path_append varName path
# Note: Forces path into the last position, if already present.
#       Duplicates are removed too, unless they're directly adjacent.
# EXAMPLE: path_append PATH /usr/local/bin
path_append() {
  local aux=":${!1}:"
  aux=${aux//:$2:/:}; aux=${aux#:}; aux=${aux%:}
  printf -v "$1" '%s' "${aux}${aux:+:}${2}"
}

# SYNOPSIS: path_remove varName path
# Note: Duplicates are removed too, unless they're directly adjacent.
# EXAMPLE: path_remove PATH /usr/local/bin
path_remove() {
  local aux=":${!1}:"
  aux=${aux//:$2:/:}; aux=${aux#:}; aux=${aux%:}
  printf -v "$1" '%s' "$aux"
}

如果您需要处理直接相邻的重复项和/或希望能够指定不同的字段分隔符,这里有更精细的功能使用<来自@konsolebox' helpful answer的强>数组技术。

# SYNOPSIS: field_prepend varName fieldVal [sep]
#   SEP defaults to ':'
# Note: Forces fieldVal into the first position, if already present.
#       Duplicates are removed, too.
# EXAMPLE: field_prepend PATH /usr/local/bin
field_prepend() {
    local varName=$1 fieldVal=$2 IFS=${3:-':'} auxArr
    read -ra auxArr <<< "${!varName}"
    for i in "${!auxArr[@]}"; do
        [[ ${auxArr[i]} == "$fieldVal" ]] && unset auxArr[i]
    done
    auxArr=("$fieldVal" "${auxArr[@]}")
    printf -v "$varName" '%s' "${auxArr[*]}"
}

# SYNOPSIS: field_append varName fieldVal [sep]
#   SEP defaults to ':'
# Note: Forces fieldVal into the last position, if already present.
#       Duplicates are removed, too.
# EXAMPLE: field_append PATH /usr/local/bin
field_append() {
    local varName=$1 fieldVal=$2 IFS=${3:-':'} auxArr
    read -ra auxArr <<< "${!varName}"
    for i in "${!auxArr[@]}"; do
        [[ ${auxArr[i]} == "$fieldVal" ]] && unset auxArr[i]
    done
    auxArr+=("$fieldVal")
    printf -v "$varName" '%s' "${auxArr[*]}"
}

# SYNOPSIS: field_remove varName fieldVal [sep]
#   SEP defaults to ':'
# Note: Duplicates are removed, too.
# EXAMPLE: field_remove PATH /usr/local/bin
field_remove() {
    local varName=$1 fieldVal=$2 IFS=${3:-':'} auxArr
    read -ra auxArr <<< "${!varName}"
    for i in "${!auxArr[@]}"; do
        [[ ${auxArr[i]} == "$fieldVal" ]] && unset auxArr[i]
    done
    printf -v "$varName" '%s' "${auxArr[*]}"
}

答案 1 :(得分:2)

export设置一个标志,指定变量应该在环境中。但是,如果它已经存在,则始终将更新传递到环境中;你不需要做任何其他事情。

因此:

PATH=/new/value:$PATH

PATH=$PATH:/new/value

...完全足够,除非您想添加自己的逻辑(就像重复数据删除一样)。

如果您只想在不存在重复值的情况下执行操作,则可以编写如下内容:

prepend() {
  local var=$1
  local val=$2
  local sep=${3:-":"}
  [[ ${!var} =~ (^|"$sep")"$val"($|"$sep") ]] && return # already present
  [[ ${!var} ]] || { printf -v "$var" '%s' "$val" && return; } # empty
  printf -v "$var" '%s%s%s' "$val" "$sep" "${!var}" # prepend
}

append() {
  local var=$1
  local val=$2
  local sep=${3:-":"}
  [[ ${!var} =~ (^|"$sep")"$val"($|"$sep") ]] && return # already present
  [[ ${!var} ]] || { printf -v "$var" '%s' "$val" && return; } # empty
  printf -v "$var" '%s%s%s' "${!var}" "$sep" "${val}" # append
}

remove() {
  local var=$1
  local val=$2
  local sep=${3:-":"}
  while [[ ${!var} =~ (^|.*"$sep")"$val"($|"$sep".*) ]]; do
    if [[ ${BASH_REMATCH[1]} && ${BASH_REMATCH[2]} ]]; then
      # match is between both leading and trailing content
      printf -v "$var" '%s%s' "${BASH_REMATCH[1]%$sep}" "${BASH_REMATCH[2]}"
    elif [[ ${BASH_REMATCH[1]} ]]; then
      # match is at the end
      printf -v "$var" "${BASH_REMATCH[1]%$sep}"
    else
      # match is at the beginning
      printf -v "$var" "${BASH_REMATCH[2]#$sep}"
    fi
  done
}

...用作:

prepend PATH /usr/local/bin
remove PATH /usr/local/bin

请注意:

  • 不需要function关键字,因为没有充分理由打破了POSIX兼容性。 (其他事情我们确实打破了POSIX,但实际上在某种程度上增加了价值)。
  • 我们的正则表达式引用了应该按字面解释的内容,并留下应该被视为正则表达式字符的内容。请注意,此行为仅在3.2之后的bash版本中完全一致;如果您需要与旧版本兼容,则需要进行一些更新。

答案 2 :(得分:0)

最简单的方法,也许最一致的方法是首先将这些字符串拆分为数组:

IFS=: read -ra T <<< "$PATH"

然后向这些数组添加元素:

# Append

T+=("$SOMETHING")

# Prepend or Insert

T=("$SOMETHING" "${T[@]}")

# Remove

for I in "${!T[@]}"; do
    if [[ ${T[I]} == "$SOMETHING" ]]; then
        unset "T[$I]"
        break  ## You can skip breaking if you want to remove all matches not just the first one.
    fi
done

之后你可以用一个安全的评估值把它放回去:

IFS=: eval 'PATH="${T[*]}"'

实际上如果你有点保守&#34;,你可以先保存IFS

OLD_IFS=$IFS; IFS=:
PATH="${T[*]}"
IFS=$OLD_IFS

功能:

shopt -s extglob

function path_append {
    local VAR=$1 ELEM=$2 T
    IFS=: read -ra T <<< "${!VAR}"
    T+=("$ELEM")
    [[ $VAR == [[:alpha:]_]*([[:alnum:]_]) ]] && IFS=: eval "$VAR=\${T[*]}"
}

function path_prepend {
    local VAR=$1 ELEM=$2 T
    IFS=: read -ra T <<< "${!VAR}"
    T=("$ELEM" "${T[@]}")
    [[ $VAR == [[:alpha:]_]*([[:alnum:]_]) ]] && IFS=: eval "$VAR=\${T[*]}"
}

function path_remove {
    local VAR=$1 ELEM=$2 T
    IFS=: read -ra T <<< "${!VAR}"
    for I in "${!T[@]}"; do
        [[ ${T[I]} == "$ELEM" ]] && unset "T[$I]"
    done
    [[ $VAR == [[:alpha:]_]*([[:alnum:]_]) ]] && IFS=: eval "$VAR=\${T[*]}"
}