web-dev-qa-db-ja.com

$ PATHにきれいに追加するにはどうすればよいですか?

同じパスを何度も追加することなく、システム全体または個々のユーザーのために$ PATHに項目を追加する方法が欲しいのですが。

これを行う理由の1つは、ログインを必要としない.bashrcで追加を行うことができるようにするためです。また、(たとえば)lightdmを使用するシステムではより便利です。 .profile

$ -PATHから 重複を削除する方法 に関する質問を知っていますが、重複を削除したくない。パスがまだ存在しない場合にのみ、パスを追加する方法を教えてください。

32
goldilocks

追加する新しいパスが次のとおりだとします。

new=/opt/bin

次に、任意のPOSIXシェルを使用して、newがすでにパスに含まれているかどうかをテストし、含まれていない場合は追加できます。

case ":${PATH:=$new}:" in
    *:"$new":*)  ;;
    *) PATH="$new:$PATH"  ;;
esac

コロンの使用に注意してください。コロンがないと、new=/bin/usr/binでパターンが一致したため、すでにパスに含まれていると考えるかもしれません。 PATHには通常多くの要素がありますが、PATHの要素が0と1の特別な場合も処理されます。 PATHの最初の要素がない(空である)場合は、${PATH:=$new}を使用して処理されます。PATHは、空の場合に$newに割り当てられます。この方法でパラメーターのデフォルト値を設定することは、すべてのPOSIXシェルの機能です。 POSIX docs のセクション2.6.2を参照してください。)

呼び出し可能な関数

便宜上、上記のコードを関数に入れることができます。この関数はコマンドラインで定義するか、永続的に使用できるようにシェルの初期化スクリプトに追加できます(bashユーザーの場合は~/.bashrcになります):

pupdate() { case ":${PATH:=$1}:" in *:"$1":*) ;; *) PATH="$1:$PATH" ;; esac; }

このパス更新機能を使用して現在のPATHにディレクトリを追加するには:

pupdate /new/path
36
John1024

/etc/profile.dという名前のファイルを作成します(例:mypath.sh(または必要なもの))。 lightdmを使用している場合は、実行可能であることを確認するか、/etc/bashrcまたは同じソースからのファイルを使用してください。さらに、次の関数を追加します。

checkPath () {
        case ":$PATH:" in
                *":$1:"*) return 1
                        ;;
        esac
        return 0;
}

# Prepend to $PATH
prependToPath () {
        for a; do
                checkPath $a
                if [ $? -eq 0 ]; then
                        PATH=$a:$PATH
                fi
        done
        export PATH
}

# Append to $PATH
appendToPath () {
        for a; do
                checkPath $a
                if [ $? -eq 0 ]; then
                        PATH=$PATH:$a
                fi
        done
        export PATH
}

$ PATHの先頭(先頭に追加)のものが後続のものより優先され、逆に、末尾(末尾)のものが優先されます。つまり、$ PATHが/usr/local/bin:/usr/binであり、両方のディレクトリに実行可能ファイルgotchaがある場合、/usr/local/binにあるものがデフォルトで使用されます。

これで、この同じファイル、別のシェル構成ファイル、またはコマンドラインから、次を使用できます。

appendToPath /some/path /another/path
prependToPath /some/path /yet/another/path

これが.bashrcにある場合、新しいシェルを開始するときに値が複数回表示されるのを防ぎます。先頭に追加されたもの(つまり、$ PATH内のパスを移動する)を追加する場合、またはその逆の場合は、自分で行う必要があるという制限があります。

9
goldilocks

あなたはこの方法でそれを行うことができます:

echo $PATH | grep /my/bin >/dev/null || PATH=$PATH:/my/bin

注:他の変数からPATHを構築する場合、多くのシェルが「」のように「」を解釈するため、それらが空でないことを確認してください。 。

5
countermode

コードの重要な部分は、PATHに特定のパスが含まれているかどうかを確認することです。

printf '%s' ":${PATH}:" | grep -Fq ":${my_path}:"

つまり、PATH内の各パスが両側PATHセパレーター(:)で区切られていることを確認してから、check-qPATHセパレーター、パス、および別のPATHセパレーターで構成されるliteral文字列(-F)がそこに存在するかどうか。そうでない場合は、安全にパスを追加できます。

if ! printf '%s' ":${PATH-}:" | grep -Fq ":${my_path-}:"
then
    PATH="${PATH-}:${my_path-}"
fi

これは POSIX互換 である必要があり、改行文字を含まないすべてのパスで機​​能します。 POSIX互換でありながら改行を含むパスを操作する場合はさらに複雑ですが、-zをサポートするgrepがある場合は、それを使用できます。

5
l0b0

私は何年もの間、この小さな機能をさまざまな~/.profileファイルで持ち歩きました。 I thinkこれは、以前使用していたラボのsysadminによって作成されましたが、よくわかりません。とにかく、それはGoldilockのアプローチに似ていますが、少し異なります。

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

したがって、PATHの先頭に新しいディレクトリを追加するには:

pathmunge /new/path

そして最後まで:

pathmunge /new/path after
4
terdon

更新:

あなたの答えには、$PATHに追加または前置するための個別の関数があることに気付きました。アイデアが気に入りました。そこで、引数の処理を少し追加しました。また、適切に_namespacedしました。

_path_assign() { oFS=$IFS ; IFS=: ; add=$* ; unset P A ; A=
    set -- ${PATH:=$1} ; for p in $add ; do {
        [ -z "${p%-[AP]}" ] && { unset P A
                eval ${p#-}= ; continue ; }
        for d ; do [ -z "${d%"$p"}" ] && break
        done ; } || set -- ${P+$p} $* ${A+$p}
        done ; export PATH="$*" ; IFS=$oFS
}

% PATH=/usr/bin:/usr/yes/bin
% _path_assign \
    /usr/bin \
    /usr/yes/bin \
    /usr/bin/nope \
    -P \
    /usr/nope/bin \
    /usr/bin \
    -A \
    /nope/usr/bin \
    /usr/nope/bin

% echo $PATH

出力:

/usr/nope/bin:/usr/bin:/usr/yes/bin:/usr/bin/nope:/nope/usr/bin

デフォルトでは-Appendを$PATHに変更しますが、引数のリストの任意の場所に-Pを追加することで、この動作を-Prependに変更できます。もう一度-Aを渡すと、-Appendingに戻すことができます。

安全評価

ほとんどの場合、evalを使用しないことをお勧めします。しかし、これはgoodの使用例として際立っています。この場合、evalできますP=またはA=を参照してください。引数の値は、呼び出される前に厳密にテストされます。これがevalの目的です

assign() { oFS=$IFS ; IFS=: ; add=$* 
    set -- ${PATH:=$1} ; for p in $add ; do { 
        for d ; do [ -z "${d%"$p"}" ] && break 
        done ; } || set -- $* $p ; done
    PATH="$*" ; IFS=$oFS
}

これは、指定した数の引数を受け入れ、$PATHにまだ追加されていない場合にのみ、それぞれを$PATHに追加します。完全に移植可能なPOSIXシェルスクリプトのみを使用し、シェル組み込みにのみ依存しており、非常に高速です。

% PATH=/usr/bin:/usr/yes/bin
% assign \
    /usr/bin \
    /usr/yes/bin \
    /usr/nope/bin \
    /usr/bin \
    /nope/usr/bin \
    /usr/nope/bin

% echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin
4
mikeserv

見よ!産業用強度の12ライン ...技術的に 選択した_~/.bashrc_または_~/.zshrc_起動スクリプトを熱心に愛するbashおよびzsh-portableシェル関数:

_# void +path.append(str dirname, ...)
#
# Append each passed existing directory to the current user's ${PATH} in a
# safe manner silently ignoring:
#
# * Relative directories (i.e., *NOT* prefixed by the directory separator).
# * Duplicate directories (i.e., already listed in the current ${PATH}).
# * Nonextant directories.
+path.append() {
    # For each passed dirname...
    local dirname
    for   dirname; do
        # Strip the trailing directory separator if any from this dirname,
        # reducing this dirname to the canonical form expected by the
        # test for uniqueness performed below.
        dirname="${dirname%/}"

        # If this dirname is either relative, duplicate, or nonextant, then
        # silently ignore this dirname and continue to the next. Note that the
        # extancy test is the least performant test and hence deferred.
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname}:"* &&
           -d "${dirname}" ]] || continue

        # Else, this is an existing absolute unique dirname. In this case,
        # append this dirname to the current ${PATH}.
        PATH="${PATH}:${dirname}"
    done

    # Strip an erroneously leading delimiter from the current ${PATH} if any,
    # a common Edge case when the initial ${PATH} is the empty string.
    PATH="${PATH#:}"

    # Export the current ${PATH} to subprocesses. Although system-wide scripts
    # already export the ${PATH} by default on most systems, "Bother free is
    # the way to be."
    export PATH
}
_

瞬間的な栄光のために自分を準備します。次に、これを行い、うまくいけば最高のものを期待するのではなく:

_export PATH=$PATH:~/opt/bin:~/the/black/goat/of/the/woods/with/a/thousand/young
_

代わりにこれを実行して、本当にそれが欲しかったかどうかに関係なく、最高のものを得ることが保証されます:

_+path.append ~/opt/bin ~/the/black/goat/of/the/woods/with/a/thousand/young
_

非常によく、「最高」を定義します。

現在の_${PATH}_に安全に追加したり前に追加したりすることは、通常行われている些細なことではありません。便利で一見賢明なように見えますが、_export PATH=$PATH:~/opt/bin_という形式のワンライナーは、次のような悪意のある複雑化を招きます。

  • 偶然に相対ディレクトリ名(例、_export PATH=$PATH:opt/bin_)。 bashzshは、mostの場合に相対ディレクトリ名を黙って受け入れ、ほとんど無視しますが、hまたはt(または他の悪意のある文字)が前に付いている相対ディレクトリ名は、両方ともMasaki自身を恥ずかしく悪用します小林さんの 1962年の名作原切

    _# Don't try this at home. You will feel great pain.
    $ PATH='/usr/local/bin:/usr/bin:/bin' && export PATH=$PATH:harakiri && echo $PATH
    /usr/local/bin:/usr/bin:arakiri
    $ PATH='/usr/local/bin:/usr/bin:/bin' && export PATH=$PATH:tanuki/yokai && echo $PATH
    binanuki/yokai   # Congratulations. Your system is now face-up in the Gutter.
    _
  • 誤って重複するdirnames。重複する_${PATH}_ dirnamesは、ほとんど無害ですが、不要で扱いにくく、わずかに非効率的で、デバッグ性を妨げ、促進しますドライブウェア–この答えのようなもの。 NANDスタイルのSSDは(もちろん)読み取り摩耗の影響を受けませんが、HDDはそうではありません。 every試行されたコマンドでの不要なファイルシステムアクセスは、同じテンポでの不要な読み取りヘッドの摩耗を意味します。ネストされたサブプロセスでネストされたシェルを呼び出す場合、重複は特にあいまいです。その時点で、_export PATH=$PATH:~/wat_のように一見無害な1行が_${PATH}_の第7サークルに急速に爆発します。_PATH=/usr/local/bin:/usr/bin:/bin:/home/leycec/wat:/home/leycec/wat:/home/leycec/wat:/home/leycec/wat_のような地獄。その後、追加のdirnameを追加すると、Belzebubbaだけが役立ちます。 (これをあなたの大切な子供たちに起こさせないでください。

  • 誤ってdirnamesが欠落しています。繰り返しますが、_${PATH}_ dirnamesが欠落している間はほとんど無害ですが、通常は望ましくなく、扱いにくく、非効率的で、デバッグ性を妨げます。 、ドライブの摩耗を促進します。

エルゴ、上で定義したシェル関数のようなフレンドリーなオートメーション。私たちは自分自身から自分を救わなければなりません。

しかし...なぜ「+ path.append()」なのか?なぜ単にappend_path()しないのですか?

あいまいさをなくすため(たとえば、現在の_${PATH}_の外部コマンドまたは別の場所で定義されているシステム全体のシェル関数を使用)、ユーザー定義のシェル関数には、bashzshでサポートされている一意のサブストリングをプレフィックスまたはサフィックスとして付けるのが理想的ですが、それ以外の場合は標準コマンドで禁止されていますベース名-たとえば、_+_など。

ねえ。できます。 私を判断しないでください。

しかし...なぜ「+ path.append()」なのか?なぜ「+ path.prepend()」ではないのですか?

現在の_${PATH}_に追加することは、現在の_${PATH}_に追加するよりも安全であるため、すべてのものが等しくなることはありません。システム全体のコマンドをユーザー固有のコマンドでオーバーライドすると、最高の状態では不衛生になり、最悪の場合は狂ったようになってしまいます。たとえば、Linuxでは、ダウンストリームアプリケーションは通常、カスタムの非標準の派生物や代替物ではなく、コマンドの GNU coreutils バリアントを期待します。

そうは言っても、そうするための正当なユースケースは絶対にあります。同等の+path.prepend()関数を定義するのは簡単です。 sans prolix nebulosity、彼と彼女の共有された正気のため:

_+path.prepend() {
    local dirname
    for dirname in "${@}"; do
        dirname="${dirname%/}"
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname}:"* &&
           -d "${dirname}" ]] || continue
        PATH="${dirname}:${PATH}"
    done
    PATH="${PATH%:}"
    export PATH
}
_

でも…なぜジル?

Gilles ' 他の場所で受け入れられた回答 は、一般的なケースでは "Shell agnostic idempotent append" として印象的に最適です。ただし、bashzshnoの望ましくないシンボリックリンクが使用される場合、パフォーマンスの低下によって Gentoo ricer が悲しくなります。望ましくないシンボリックリンクが存在する場合でも、add_to_PATH()引数ごとに1つのサブシェルをフォークすることが、シンボリックリンクの重複を挿入する可能性があるかどうかについては議論の余地があります。

シンボリックリンクの重複を排除することを要求する厳密なユースケースの場合、このzsh固有のバリアントは、非効率的なフォークではなく効率的な組み込みを介して行います。

_+path.append() {
    local dirname
    for   dirname in "${@}"; do
        dirname="${dirname%/}"
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname:A}:"* &&
           -d "${dirname}" ]] || continue
        PATH="${PATH}:${dirname}"
    done
    PATH="${PATH#:}"
    export PATH
}
_

オリジナルの_*":${dirname:A}:"*_ではなく_*":${dirname}:"*_に注意してください。 _:A_は、zshを含む他のほとんどのシェルでは残念ながら存在しない、すばらしいbash- ismです。 _man zshexpn_を引用するには:

[〜#〜] a [〜#〜]a修飾子のようにファイル名を絶対パスに変換し、結果をシンボリックリンクを解決するrealpath(3)ライブラリ関数。注:realpath(3)ライブラリ関数を持たないシステムでは、シンボリックリンクは解決されないため、これらのシステムではaAは同等です。

それ以上の質問はありません。

どういたしまして。安全な砲撃をお楽しみください。あなたは今それに値する。

0
Cecil Curry

このスクリプトを使用すると、$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" ] && [ "$prefix" != "" ] && PATH=$prefix:$PATH
}
0
Tom Hale

これが関数型プログラミングスタイルのバージョンです。

  • PATHだけでなく、コロン区切りの*PATH変数でも機能します。
  • グローバル状態にアクセスしません
  • 与えられた不変の入力でのみ動作します
  • 単一の出力を生成します
  • 副作用なし
  • メモ可能(原則)

また注目に値する:

  • exportingにとらわれない;それは呼び出し元に任されます(例を参照)
  • 純粋bash;フォークなし
 path_add(){
#$ 1:指定したパス文字列に1回だけ存在することを確認する要素
#$ 2:既存のパス文字列値(「$ PATH」ではなく「$ PATH」 )
#$ 3(オプション、何でも):与えられた場合、$ 1を追加します。それ以外の場合は、先頭に追加します
#
#例:
#$ export PATH = $(path_add '/ opt/bin' "$ PATH")
#$ CDPATH = $(path_add '/ Music' "$ CDPATH" at_end)
 
 local -r already_present = "(^ |:)$ {1}($ | :)" 
 if [["$ 2" =〜$ already_present]]; then 
 echo "$ 2" 
 Elif [[$#== 3]];次に、
 echo "$ {2}:$ {1}" 
 else 
 echo "$ {1}:$ {2}" 
 fi 
} 
0
Phil Hudson