同じパスを何度も追加することなく、システム全体または個々のユーザーのために$ PATHに項目を追加する方法が欲しいのですが。
これを行う理由の1つは、ログインを必要としない.bashrc
で追加を行うことができるようにするためです。また、(たとえば)lightdm
を使用するシステムではより便利です。 .profile
。
$ -PATHから 重複を削除する方法 に関する質問を知っていますが、重複を削除したくない。パスがまだ存在しない場合にのみ、パスを追加する方法を教えてください。
追加する新しいパスが次のとおりだとします。
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
/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内のパスを移動する)を追加する場合、またはその逆の場合は、自分で行う必要があるという制限があります。
あなたはこの方法でそれを行うことができます:
echo $PATH | grep /my/bin >/dev/null || PATH=$PATH:/my/bin
注:他の変数からPATHを構築する場合、多くのシェルが「」のように「」を解釈するため、それらが空でないことを確認してください。 。
コードの重要な部分は、PATH
に特定のパスが含まれているかどうかを確認することです。
printf '%s' ":${PATH}:" | grep -Fq ":${my_path}:"
つまり、PATH
内の各パスが両側でPATH
セパレーター(:
)で区切られていることを確認してから、check(-q
)PATH
セパレーター、パス、および別のPATH
セパレーターで構成されるliteral文字列(-F
)がそこに存在するかどうか。そうでない場合は、安全にパスを追加できます。
if ! printf '%s' ":${PATH-}:" | grep -Fq ":${my_path-}:"
then
PATH="${PATH-}:${my_path-}"
fi
これは POSIX互換 である必要があり、改行文字を含まないすべてのパスで機能します。 POSIX互換でありながら改行を含むパスを操作する場合はさらに複雑ですが、-z
をサポートするgrep
がある場合は、それを使用できます。
私は何年もの間、この小さな機能をさまざまな~/.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
あなたの答えには、$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
デフォルトでは-A
ppendを$PATH
に変更しますが、引数のリストの任意の場所に-P
を追加することで、この動作を-P
rependに変更できます。もう一度-A
を渡すと、-A
ppendingに戻すことができます。
ほとんどの場合、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
見よ!産業用強度の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
_)。 bash
とzsh
は、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だけが役立ちます。 (これをあなたの大切な子供たちに起こさせないでください。)
${PATH}
_ dirnamesが欠落している間はほとんど無害ですが、通常は望ましくなく、扱いにくく、非効率的で、デバッグ性を妨げます。 、ドライブの摩耗を促進します。エルゴ、上で定義したシェル関数のようなフレンドリーなオートメーション。私たちは自分自身から自分を救わなければなりません。
あいまいさをなくすため(たとえば、現在の_${PATH}
_の外部コマンドまたは別の場所で定義されているシステム全体のシェル関数を使用)、ユーザー定義のシェル関数には、bash
とzsh
でサポートされている一意のサブストリングをプレフィックスまたはサフィックスとして付けるのが理想的ですが、それ以外の場合は標準コマンドで禁止されていますベース名-たとえば、_+
_など。
ねえ。できます。 私を判断しないでください。
現在の_${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" として印象的に最適です。ただし、bash
とzsh
でnoの望ましくないシンボリックリンクが使用される場合、パフォーマンスの低下によって 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)
ライブラリ関数を持たないシステムでは、シンボリックリンクは解決されないため、これらのシステムではa
とA
は同等です。
どういたしまして。安全な砲撃をお楽しみください。あなたは今それに値する。
このスクリプトを使用すると、$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
}
これが関数型プログラミングスタイルのバージョンです。
PATH
だけでなく、コロン区切りの*PATH
変数でも機能します。また注目に値する:
export
ingにとらわれない;それは呼び出し元に任されます(例を参照)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 }