web-dev-qa-db-ja.com

$ PATHにディレクトリがまだない場合は追加します

$ PATHにディレクトリがまだない場合にのみ、ディレクトリを追加するbash関数を記述した人はいますか?

私は通常、次のようなものを使用してPATHに追加します。

_export PATH=/usr/local/mysql/bin:$PATH
_

.bash_profileにPATHを作成した場合、現在のセッションがログインセッションでない限り読み込まれません。これは常に正しいとは限りません。 .bashrcにPATHを作成すると、各サブシェルで実行されます。したがって、ターミナルウィンドウを起動してからscreenを実行してからシェルスクリプトを実行すると、次のようになります。

_$ echo $PATH
/usr/local/mysql/bin:/usr/local/mysql/bin:/usr/local/mysql/bin:....
_

add_to_path()というbash関数を作成してみます。この関数は、ディレクトリがない場合にのみディレクトリを追加します。しかし、誰かがそのようなことをすでに書いた(または見つけた)場合は、それに時間を費やすことはありません。

129
Doug Harris

私の.bashrcから:

pathadd() {
    if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
        PATH="${PATH:+"$PATH:"}$1"
    fi
}

PATHはすでにエクスポート済みとしてマークされている必要があるため、再エクスポートする必要はありません。これは、ディレクトリが存在するかどうか、およびそれがディレクトリであるかどうかを、追加する前にチェックします。

また、これにより、パスの最後に新しいディレクトリが追加されます。最初に置くには、PATH="$1${PATH:+":$PATH"}"上記の代わりにPATH=行。

129
Gordon Davisson

Gordon Davissonの答えを拡張して、これは複数の引数をサポートします

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

したがって、pathappend path1 path2 path3 ...

先頭に、

pathprepend() {
  for ((i=$#; i>0; i--)); 
  do
    ARG=${!i}
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="$ARG${PATH:+":$PATH"}"
    fi
  done
}

Pathappendと同様に、次のことができます

pathprepend path1 path2 path3 ...

my answer から this question までをDoug Harrisの関数の構造と組み合わせたものを以下に示します。 Bash正規表現を使用します。

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}

これを選択した回答のコメントに入れますが、コメントはPREのフォーマットをサポートしていないようなので、ここに回答を追加します。

@ gordon-davisson私は不必要な引用と連結の大ファンではありません。 bashバージョン> = 3を使用していると仮定すると、代わりにbashの組み込み正規表現を使用して次のことができます。

pathadd() {
    if [ -d "$1" ] && [[ ! $PATH =~ (^|:)$1(:|$) ]]; then
        PATH+=:$1
    fi
}

これは、ディレクトリまたはPATHにスペースがある場合を正しく処理します。 bashの組み込みの正規表現エンジンが遅いので、バージョンが行う文字列の連結と補間よりも効率が悪いのではないかという疑問がありますが、どういうわけか私には美的にきれいに感じられます。

10
idempotent_path_prepend ()
{
    PATH=${PATH//":$1"/} #delete any instances in the middle or at the end
    PATH=${PATH//"$1:"/} #delete any instances at the beginning
    export PATH="$1:$PATH" #prepend to beginning
}

$ HOME/binが$ PATHの最初に1回だけ出現し、それ以外の場所に出現しないようにする必要がある場合は、代替を受け入れません。

7
Russell

冗長な全体を削除するという追加の利点がある代替ソリューションを次に示します。

function pathadd {
    PATH=:$PATH
    PATH=$1${PATH//:$1/}
}

この関数への単一の引数がPATHの前に追加され、同じ文字列の最初のインスタンスが既存のパスから削除されます。つまり、ディレクトリがパスにすでに存在する場合、複製として追加されるのではなく、前面に昇格されます。

この関数は、パスの前にコロンを追加してすべてのエントリの前にコロンがあることを確認し、既存のパスの前に新しいエントリを追加して、そのエントリを削除します。最後の部分は、bashの${var//pattern/sub}表記を使用して実行されます。詳細は bashマニュアル を参照してください。

6
Rob Hague

先頭に付けるには、@ Russellのソリューションが好きですが、小さなバグがあります。「/ bin」のようなものを「/ sbin:/ usr/bin:/ var/usr/bin:/ usr/local」のパスに追加しようとすると/ bin:/ usr/sbin ""/bin: "を3回置き換えます(まったく一致しなかった場合)。そのための修正と@ gordon-davissonの追加ソリューションを組み合わせると、次のようになります。

path_prepend() {
    if [ -d "$1" ]; then
        PATH=${PATH//":$1:"/:} #delete all instances in the middle
        PATH=${PATH/%":$1"/} #delete any instance at the end
        PATH=${PATH/#"$1:"/} #delete any instance at the beginning
        PATH="$1${PATH:+":$PATH"}" #prepend $1 or if $PATH is empty set to $1
    fi
}
5
PeterS6g

これは私のものです(私の昔の研究室のシステム管理者であるOscarが何年も前に書いたものだと思いますが、すべて彼の功績です)。必要に応じて、新しいディレクトリの先頭または末尾に追加できるという追加の利点があります。

pathmunge () {
        if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
           if [ "$2" = "after" ] ; then
              PATH=$PATH:$1
           else
              PATH=$1:$PATH
           fi
        fi
}

使用法:

$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /bin/
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /sbin/ after
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin:/sbin/
5
terdon

以下のような単純なエイリアスでうまくいくはずです。

alias checkInPath="echo $PATH | tr ':' '\n' | grep -x -c "

これは、パスを:文字で分割し、各コンポーネントを渡した引数と比較するだけです。grepは、完全な行の一致をチェックし、カウントを出力します。

使用例:

$ checkInPath "/usr/local"
1
$ checkInPath "/usr/local/sbin"
1
$ checkInPath "/usr/local/sbin2"
0
$ checkInPath "/usr/local/" > /dev/null && echo "Yes" || echo "No"
No
$ checkInPath "/usr/local/bin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin2" > /dev/null && echo "Yes" || echo "No"
No

EchoコマンドをaddToPathまたは同様のエイリアス/関数に置き換えます。

4
user4358

これが私が打ち上げたものです:

add_to_path ()
{
    path_list=`echo $PATH | tr ':' ' '`
    new_dir=$1
    for d in $path_list
    do
        if [ $d == $new_dir ]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}

今.bashrcでは私が持っています:

add_to_path /usr/local/mysql/bin

更新バージョンオリジナルがスペースを含むディレクトリを処理しない方法についてのコメント(ありがとう この質問 を使用してIFS):

add_to_path ()
{
    new_dir=$1
    local IFS=:
    for d in $PATH
    do
        if [[ "$d" == "$new_dir" ]]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}
2
Doug Harris

この質問に対する一連の回答については、StackOverflowで cshでパス変数が重複しないようにする方法 を参照してください。

2

まだ誰もこれについて言及していないことに少し驚いていますが、readlink -fを使用して相対パスを絶対パスに変換し、それらをPATHに追加することができます。

たとえば、Guillaume Perrault-Archambaultの答えを改善するには、

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

なる

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]
        then
            if ARGA=$(readlink -f "$ARG")               #notice me
            then
                if [ -d "$ARGA" ] && [[ ":$PATH:" != *":$ARGA:"* ]]
                then
                    PATH="${PATH:+"$PATH:"}$ARGA"
                fi
            else
                PATH="${PATH:+"$PATH:"}$ARG"
            fi
        fi
    done
}

1.基本—これは何をするのですか?

readlink -fコマンドは、(とりわけ)相対パスを絶対パスに変換します。これにより、次のようなことができます

$ cd /path/to/my/bin/dir
$ pathappend 
 $ echo "$ PATH" 
<your_old_path>:/パス/ to/my/bin/dir

2.なぜPATHを2回行うかをテストするのですか?

さて、上記の例を考えてみましょう。ユーザーがpathappend .ディレクトリから/path/to/my/bin/dirをもう一度言うと、ARG.になります。もちろん、.PATHには存在しません。しかし、ARGA/path/to/my/bin/dir.と同等の絶対パス)に設定され、 is PATH。したがって、/path/to/my/bin/dirPATHに再度追加することを避ける必要があります。

おそらくより重要なこととして、readlinkの主な目的は、その名前が示すように、シンボリックリンクを調べ、そこに含まれる(つまり、ポイントする)パス名を読み取ることです。例えば:

$ ls -ld /usr/lib/Perl/5.14
-rwxrwxrwx  1   root   root    Sep  3  2015 /usr/lib/Perl/5.14 -> 5.14.2
$ readlink /usr/lib/Perl/5.14
5.14.2
$ readlink -f /usr/lib/Perl/5.14
/usr/lib/Perl/5.14.2

ここで、pathappend /usr/lib/Perl/5.14と言い、PATHに/usr/lib/Perl/5.14がすでにある場合は、それで問題ありません。そのままにしておくことができます。ただし、/usr/lib/Perl/5.14がまだPATHにない場合は、readlinkを呼び出してARGA = /usr/lib/Perl/5.14.2を取得し、 that PATHに追加します。しかし、少し待ってください— すでにpathappend /usr/lib/Perl/5.14と言った場合、PATHに/usr/lib/Perl/5.14.2がすでにあります。もう一度、PATHに追加しないように、もう一度確認する必要があります。 。

3. if ARGA=$(readlink -f "$ARG")との関係は?

不明な場合は、この行でreadlinkが成功するかどうかをテストします。これは、優れた防御的なプログラミング手法です。コマンドの出力 m part of コマンド n (ここで m << n ))、それは賢明ですコマンド m が失敗したかどうかを確認し、何らかの方法で処理します。 readlinkが失敗する可能性は低いと思います— OS/X などから任意のファイルの絶対パスを取得する方法で説明されているように、readlinkはGNU発明。POSIXでは指定されていないため、Mac OS、Solaris、およびその他のLinux以外のUnixでの利用には疑問がある(実際には、 a comment を読んだだけである) 「readlink -fはMac OS X 10.11.6では機能しないようですが、realpathはそのままでは機能します」したがって、readlinkがないシステムを使用している場合、またはreadlink -fが機能しない場合は、 realpathを使用するようにこのスクリプトを変更できる場合があります。)セーフティネットをインストールすることで、コードの移植性が多少向上します。

もちろん、readlink(またはrealpath)がないシステムを使用している場合は、pathappend .を実行する必要はありません。

2番目の-dテスト([ -d "$ARGA" ])はおそらく不要です。 $ARGがディレクトリであり、readlinkは成功するが、$ARGAがディレクトリではないシナリオは考えられません。最初のifステートメントをコピーして貼り付けて3番目のステートメントを作成し、-dテストを怠惰なままにしました。

4.その他のコメントはありますか?

うん。ここでの他の多くの回答と同様に、これは各引数がディレクトリであるかどうかをテストし、ディレクトリである場合は処理し、そうでない場合は無視します。 「.」ファイル(.bash_profile.bashrcなど)やその他のスクリプトでのみpathappendを使用している場合、これは適切な場合とそうでない場合があります。しかし、この回答が示したように(上記)、インタラクティブに使用することは完全に実現可能です。そうするなら、あなたはとても困惑するでしょう

$ pathappend /usr/local/nysql/bin
$ mysql
-bash: mysql: command not found

nysqlではなくpathappendコマンドでmysqlと言ったことに気づきましたか?そして、pathappendは何も言わなかった。それは単に間違った引数を黙って無視しましたか?

上で述べたように、エラーを処理することは良い習慣です。次に例を示します。

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ]
        then
            if [[ ":$PATH:" != *":$ARG:"* ]]
            then
                if ARGA=$(readlink -f "$ARG")           #notice me
                then
                    if [[ ":$PATH:" != *":$ARGA:"* ]]
                    then
                        PATH="${PATH:+"$PATH:"}$ARGA"
                    fi
                else
                    PATH="${PATH:+"$PATH:"}$ARG"
                fi
            fi
        else
            printf "Error: %s is not a directory.\n" "$ARG" >&2
        fi
    done
}
2
qwertyzw

この方法はうまくいきます:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
1
Akceptor
function __path_add(){  
if [ -d "$1" ] ; then  
    local D=":${PATH}:";   
    [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  
1
GreenFox

私のバージョンは、空のパスについて注意深くなく、パスが有効であることを主張し、ディレクトリをここに掲載するよりも強く主張していますが、私はprepend/append/clean/unique-ify/etcの大規模なコレクションを見つけました。パス操作に役立つシェル関数。現在の状態の全ロットはここにあります: http://Pastebin.com/xS9sgQsX (フィードバックと改善は大歓迎です!)

0
andybuckley

以下はPOSIX準拠の方法です。

# USAGE: path_add [include|prepend|append] "dir1" "dir2" ...
#   prepend: add/move to beginning
#   append:  add/move to end
#   include: add to end of PATH if not already included [default]
#          that is, don't change position if already in PATH
# RETURNS:
# prepend:  dir2:dir1:OLD_PATH
# append:   OLD_PATH:dir1:dir2
# If called with no paramters, returns PATH with duplicate directories removed
path_add() {
    # use subshell to create "local" variables
    PATH="$(path_unique)"
    export PATH="$(path_add_do "$@")"
}

path_add_do() {
    case "$1" in
    'include'|'prepend'|'append') action="$1"; shift ;;
    *)                            action='include'   ;;
    esac

    path=":$PATH:" # pad to ensure full path is matched later

    for dir in "$@"; do
        #       [ -d "$dir" ] || continue # skip non-directory params

        left="${path%:$dir:*}" # remove last occurrence to end

        if [ "$path" = "$left" ]; then
            # PATH doesn't contain $dir
            [ "$action" = 'include' ] && action='append'
            right=''
        else
            right=":${path#$left:$dir:}" # remove start to last occurrence
        fi

        # construct path with $dir added
        case "$action" in
            'prepend') path=":$dir$left$right" ;;
            'append')  path="$left$right$dir:" ;;
        esac
    done

    # strip ':' pads
    path="${path#:}"
    path="${path%:}"

    # return
    printf '%s' "$path"
}

# USAGE: path_unique [path]
# path - a colon delimited list. Defaults to $PATH is not specified.
# RETURNS: `path` with duplicated directories removed
path_unique() {
    in_path=${1:-$PATH}
    path=':'

    # Wrap the while loop in '{}' to be able to access the updated `path variable
    # as the `while` loop is run in a subshell due to the piping to it.
    # https://stackoverflow.com/questions/4667509/Shell-variables-set-inside-while-loop-not-visible-outside-of-it
    printf '%s\n' "$in_path" \
    | /bin/tr -s ':' '\n'    \
    | {
            while read -r dir; do
                left="${path%:$dir:*}" # remove last occurrence to end
                if [ "$path" = "$left" ]; then
                    # PATH doesn't contain $dir
                    path="$path$dir:"
                fi
            done
            # strip ':' pads
            path="${path#:}"
            path="${path%:}"
            # return
            printf '%s\n' "$path"
        }
}

Guillaume Perrault-Archambault の回答 この質問への回答 および mike511 の回答 ここ から引用されます。

更新2017-11-23:@スコットごとのバグを修正

0
go2null

このスクリプトを使用すると、$PATHの最後に追加できます。

PATH=path2; add_to_PATH after path1 path2:path3
echo $PATH
path2:path1:path3

または、$PATHの先頭に追加:

PATH=path2; add_to_PATH before path1 path2:path3
echo $PATH
path1:path3:path2

# Add directories to $PATH iff they're not already there
# Append directories to $PATH by default
# Based on https://unix.stackexchange.com/a/4973/143394
# and https://unix.stackexchange.com/a/217629/143394
add_to_PATH () {
  local prepend  # Prepend to path if set
  local prefix   # Temporary prepended path
  local IFS      # Avoid restoring for added laziness

  case $1 in
    after)  shift;; # Default is to append
    before) prepend=true; shift;;
  esac

  for arg; do
    IFS=: # Split argument by path separator
    for dir in $arg; do
      # Canonicalise symbolic links
      dir=$({ cd -- "$dir" && { pwd -P || pwd; } } 2>/dev/null)
      if [ -z "$dir" ]; then continue; fi  # Skip non-existent directory
      case ":$PATH:" in
        *":$dir:"*) :;; # skip - already present
        *) if [ "$prepend" ]; then
           # ${prefix:+$prefix:} will expand to "" if $prefix is empty to avoid
           # starting with a ":".  Expansion is "$prefix:" if non-empty.
            prefix=${prefix+$prefix:}$dir
          else
            PATH=$PATH:$dir  # Append by default
          fi;;
      esac
    done
  done
  [ "$prepend" ] && PATH=$prefix:$PATH
}
0
Tom Hale

カスタム変数が設定されているかどうかを確認できます。それ以外の場合は設定してから、新しいエントリを追加します。

if [ "$MYPATHS" != "true" ]; then
    export MYPATHS="true"
    export PATH="$PATH:$HOME/bin:"

    # Java stuff
    export Java_HOME="$(/usr/libexec/Java_home)"
    export M2_HOME="$HOME/Applications/Apache-maven-3.3.9"
    export PATH="$Java_HOME/bin:$M2_HOME/bin:$PATH"

    # etc...
fi

もちろん、/etc/profileなどの別のスクリプトによって追加された場合、これらのエントリは重複する可能性があります。

0
Big McLargeHuge

Perlの1つのライナーを使用できます。

appendPaths() { # append a group of paths together, leaving out redundancies
    # use as: export PATH="$(appendPaths "$PATH" "dir1" "dir2")
    # start at the end:
    #  - join all arguments with :,
    #  - split the result on :,
    #  - pick out non-empty elements which haven't been seen and which are directories,
    #  - join with :,
    #  - print
    Perl -le 'print join ":", grep /\w/ && !$seen{$_}++ && -d $_, split ":", join ":", @ARGV;' "$@"
}

ここではbashにあります:

addToPath() { 
    # inspired by Gordon Davisson, http://superuser.com/a/39995/208059
    # call as: addToPath dir1 dir2
    while (( "$#" > 0 )); do
    echo "Adding $1 to PATH."
    if [[ ! -d "$1" ]]; then
        echo "$1 is not a directory.";
    Elif [[ ":$PATH:" == *":$1:"* ]]; then
        echo "$1 is already in the path."
    else
            export PATH="${PATH:+"$PATH:"}$1" # ${x:-defaultIfEmpty} ${x:+valueIfNotEmpty}
    fi
    shift
    done
}
0
Daniel Patru

Gordon Davisson's answer を少し修正して、何も指定されていない場合は現在のディレクトリを使用するようにしました。したがって、PATHに追加したいディレクトリからpaddを実行するだけです。

padd() {
  current=`pwd`
  p=${1:-$current}
  if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
      PATH="$p:$PATH"
  fi
}
0
Thorsten Lorenz