bash – ディレクトリがない場合に $PATH に追加する

bash path script

誰か$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


タイトルとURLをコピーしました