誰か$PATHにディレクトリがない場合にだけ$PATHにディレクトリを追加するbash関数を書いた人はいませんか?
私は通常、次のようなものを使ってPATHに追加します
export PATH=/usr/local/mysql/bin:$PATH
.bash_profile で自分の PATH を構築した場合、今いるセッションがログインセッションでない限り読み込まれません。PATH を .bashrc で構築した場合、サブシェルごとに実行されます。つまり、ターミナルウィンドウを起動して screen を実行し、シェルスクリプトを実行すると、以下のようになります
$ echo $PATH
/usr/local/mysql/bin:/usr/local/mysql/bin:/usr/local/mysql/bin:....
add_to_path()
という、ディレクトリがない場合にだけディレクトリを追加するbash関数を作ってみようと思います。しかし、もし誰かが既にそんなものを書いている(見つけた)人がいたら、私はそのために時間を費やすことはありません
137 Doug Harris 2009-09-11
私の.bashrcから
pathadd() {
if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
PATH="${PATH:+"$PATH:"}$1"
fi
}
PATHはすでにエクスポートされたものとしてマークされているはずなので、再エクスポートは必要ないことに注意してください。これは、追加する前にディレクトリが存在するかどうかをチェックします
また、これは新しいディレクトリをパスの最後に追加します; 先頭に置くには、上記の PATH=
行の代わりに PATH="$1${PATH:+":$PATH"}"
を使用します
136 Gordon Davisson 2009-09-12
ゴードン・ダヴィソンの回答を拡大してみると、複数の主張を裏付けることができます
pathappend() {
for ARG in "$@"
do
if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
PATH="${PATH:+"$PATH:"}$ARG"
fi
done
}
ですから、pathappend path1 path2 path3 …を実行することができます
For prepending,
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
23 Guillaume Perrault-Archambault 2014-05-15
ここでは、私の回答からこの質問まで、Doug Harrisの関数の構造と組み合わせたものを紹介します。これは Bash の正規表現を使っています
add_to_path ()
{
if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
then
return 0
fi
export PATH=${1}:$PATH
}
14 Paused until further notice. 2009-09-11
選択した解答にコメントに入れますが、コメントはPREフォーマットに対応していないようなので、ここに追加します
@gordon-davisson 私は不必要なクォート&の連結はあまり好きではありません。bash のバージョン >= 3 を使用していると仮定すると、代わりに bash の組み込みの正規表現を使用して do を行うことができます
pathadd() {
if [ -d "$1" ] && [[ ! $PATH =~ (^|:)$1(:|$) ]]; then
PATH+=:$1
fi
}
これは、ディレクトリや PATH にスペースがある場合を正しく処理します。bash の組み込み正規表現エンジンが遅いので、あなたのバージョンのような文字列の連結や補間よりも効率が悪いのではないかという疑問がありますが、何となく私にはより綺麗に感じます
11 Christopher Smith 2012-03-02
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 の先頭に一度だけ、他のどこにも表示させたくない場合は、代用できません
7 Russell 2012-08-17
ここでは、冗長なエンティルを削除するという追加の利点を持つ代替ソリューションをご紹介します
function pathadd {
PATH=:$PATH
PATH=$1${PATH//:$1/}
}
この関数への単一引数は、PATHに前置され、同じ文字列の最初のインスタンスが既存のパスから削除されます。つまり、そのディレクトリが既にパスに存在する場合は、重複して追加されるのではなく、前に昇格されます
この関数は、すべてのエントリがコロンを先頭に持つようにパスにコロンを前置し、そのエントリを削除した新しいエントリを既存のパスに前置することで動作します。最後の部分は bash の ${var//pattern/sub}
記法を用いて実行されます。詳細は bash マニュアル を参照してください
6 Rob Hague 2015-01-21
これは私のものです(私の古い研究室のシステム管理者であるオスカーが何年も前に書いたものだと思いますが、全て彼の功績です)。これは、必要に応じて新しいディレクトリの前に追加したり、追加したりすることができるという利点があります
pathmunge () {
if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
if [ "$2" = "after" ] ; then
PATH=$PATH:$1
else
PATH=$1:$PATH
fi
fi
}
Usage:
$ 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 2012-08-17
前置詞については、@Russellさんの解決策が気に入っていますが、小さなバグがあります。この修正と @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 2013-11-19
以下のようなシンプルなエイリアスでトリックを行う必要があります
alias checkInPath="echo $PATH | tr ':' '\n' | grep -x -c "
これは : 文字のパスを分割し、各コンポーネントを引数と比較します
Sample usage:
$ 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 None 2009-09-11
この質問に対する回答は StackOverflow の How to keep from duplicating path variable in csh? を参照してください
2 Jonathan Leffler 2009-09-11
この方法でも問題ありません
if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
2 Akceptor 2014-07-28
これが私が作ったものだ
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 2009-09-11
まだ誰も言及していないのでちょっと驚いていますが、readlink -f
を使って相対パスを絶対パスに変換して、そのようにPATHに追加することができます
例えば、ギョーム・ペロー=アシャンボーの答えを改善するには
pathappend() {
for ARG in "$@"
do
if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
PATH="${PATH:+"$PATH:"}$ARG"
fi
done
}
becomes
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>:/path/to/my/bin/dir
2.なぜPATHに2回いるかをテストするのか?
さて、上の例を考えてみましょう。ユーザーが/path/to/my/bin/dir
ディレクトリのpathappend .
を2回目に言うと、ARG
は.
になります。もちろん、.
はPATH
には存在しません。しかし、そうすると、ARGA
はすでにPATH
にある/path/to/my/bin/dir
(.
の絶対パス相当)に設定されてしまいます。そのため、PATH
に/path/to/my/bin/dir
を二度と追加しないようにする必要があります
おそらくもっと重要なことは、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
と言った場合で、すでに /usr/lib/perl/5.14
が PATH にある場合は、それで構いません。しかし、もし /usr/lib/perl/5.14
がすでに PATH にない場合は、readlink
を呼び出して ARGA
= /usr/lib/perl/5.14.2
を得て、それを PATH
に追加します。しかし、ちょっと待ってください – もしあなたがすでに pathappend /usr/lib/perl/5.14
と言っていたならば、あなたの PATH にはすでに /usr/lib/perl/5.14.2
があります
3.if ARGA=$(readlink -f "$ARG")
はどうなっているのか?
念のため、この行はreadlink
が成功するかどうかをテストしています。これは単に良い、防御的なプログラミングの練習です。もし、コマンド m の出力をコマンド 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.他に何かコメントはありますか?
そうですねここの他の多くの回答と同様に、これは各引数がディレクトリであるかどうかをテストし、ディレクトリであれば処理し、そうでなければ無視します。pathappend
を”.
“ファイル(.bash_profile
や.bashrc
のような)や他のスクリプトでのみ使用している場合は、これで十分かもしれません(あるいは、そうでないかもしれません)。しかし、この回答が示しているように(上)、インタラクティブに使うことは完全に可能です。そうすると非常に戸惑うことになります
$ pathappend /usr/local/nysql/bin
$ mysql
-bash: mysql: command not found
私が
mysql
ではなくpathappend
コマンドでnysql
と言ったことに気がつきましたか?そして、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 2017-08-22
function __path_add(){
if [ -d "$1" ] ; then
local D=":${PATH}:";
[ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
fi
}
1 GreenFox 2012-08-12
私のバージョンでは、ここに投稿されているいくつかのバージョンに比べて、空のパスやパスが有効であることやディレクトリであることを主張することにはあまり注意を払っていませんが、私は prepend/append/clean/unique-ify/etc. のシェル関数の大規模なコレクションがパス操作に便利であると感じています。現在の状態では、すべての関数がここにあります。http://pastebin.com/xS9sgQsX (フィードバックや改善は大歓迎です!)
0 andybuckley 2013-10-27
perlのワンライナーが使えます
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 2015-05-22
Gordon Davisson氏の回答を少し修正して、もし何も指定されていない場合は現在のディレクトリを使用するようにしました。だから、あなたはちょうどあなたのPATHに追加したいディレクトリからpadd
を行うことができます
padd() {
current=`pwd`
p=${1:-$current}
if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
PATH="$p:$PATH"
fi
}
0 Thorsten Lorenz 2015-06-15
カスタム変数が設定されているかどうかを確認し、そうでなければ設定してから新しいエントリを追加することができます
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 2016-06-28
このスクリプトでは、$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 2017-11-17
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の回答こちらから引用しています
UPDATE 2017-11-23.バグを@Scottあたりで修正しました
0 go2null 2015-06-08