هل قام أي شخص بكتابة دالة bash لإضافة دليل إلى PATH $ فقط إذا لم يكن موجودًا بالفعل؟
أنا عادة أضف إلى PATH باستخدام شيء مثل:
export PATH=/usr/local/mysql/bin:$PATH
إذا قمت بإنشاء PATH في .bash_profile ، فلن تتم القراءة إلا إذا كانت الجلسة التي أقوم بها عبارة عن جلسة تسجيل دخول - وهذا غير صحيح دائمًا. إذا قمت بإنشاء PATH في .bashrc ، فسيتم تشغيله مع كل مجموعة فرعية. لذلك إذا قمت بتشغيل نافذة طرفية ثم قمت بتشغيل الشاشة ثم قمت بتشغيل برنامج شل ، فسوف أحصل على:
$ echo $PATH
/usr/local/mysql/bin:/usr/local/mysql/bin:/usr/local/mysql/bin:....
سأحاول إنشاء دالة bash تسمى add_to_path()
والتي تضيف الدليل فقط إذا لم يكن هناك. ولكن ، إذا كان أي شخص قد كتب بالفعل (أو وجد) مثل هذا الشيء ، فلن أقضي الوقت في ذلك.
من بلدي .bashrc:
pathadd() {
if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
PATH="${PATH:+"$PATH:"}$1"
fi
}
لاحظ أنه يجب وضع علامة على PATH بالفعل على أنها تصدير ، لذلك ليست هناك حاجة لإعادة التصدير. يؤدي هذا إلى التحقق مما إذا كان الدليل موجودًا وهو دليل قبل إضافته ، وهو أمر قد لا يهمك.
هذا أيضًا يضيف الدليل الجديد إلى نهاية المسار ؛ لوضعه في البداية ، استخدم PATH="$1${PATH:+":$PATH"}"
بدلاً من السطر PATH=
أعلاه.
التوسع في إجابة جوردون دافيسون ، وهذا يدعم حجج متعددة
pathappend() {
for ARG in "[email protected]"
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 ...
إليك شيء من إجابتي إلى هذا السؤال مقترنًا بهيكل دوغ وظيفة هاريس. يستخدم تعبيرات Bash العادية:
add_to_path ()
{
if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
then
return 0
fi
export PATH=${1}:$PATH
}
ضع هذا في التعليقات على الإجابة المحددة ، ولكن لا يبدو أن التعليقات تدعم تنسيق PRE ، لذلك أضف الإجابة هنا:
@ gordon-davisson أنا لست معجبًا كبيرًا بالاقتباس والتسلسل غير الضروريين. على افتراض أنك تستخدم إصدار bash> = 3 ، يمكنك بدلاً من ذلك استخدام bash المضمنة في regexs والقيام بما يلي:
pathadd() {
if [ -d "$1" ] && [[ ! $PATH =~ (^|:)$1(:|$) ]]; then
PATH+=:$1
fi
}
هذا يعالج بشكل صحيح الحالات حيث توجد مسافات في الدليل أو PATH. هناك بعض التساؤلات حول ما إذا كان محرك bash المدمج في محرك regex بطيئًا بدرجة كافية بحيث يكون هذا الشبكة أقل كفاءة من تسلسل السلسلة والاستيفاء الذي تقوم به نسختك ، لكن بطريقة ما تشعر أنها أكثر نظافة جماليًا بالنسبة لي.
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 $ وفي أي مكان آخر ، لا تقبل أي بدائل.
إليك حل بديل يتمتع بميزة إضافية تتمثل في إزالة التكرار الزائد:
function pathadd {
PATH=:$PATH
PATH=$1${PATH//:$1/}
}
الوسيطة الفردية لهذه الوظيفة يتم تعليقها مسبقًا على PATH ، وتتم إزالة المثيل الأول من نفس السلسلة من المسار الموجود. بمعنى آخر ، إذا كان الدليل موجودًا بالفعل في المسار ، فسيتم ترقيته إلى المقدمة بدلاً من إضافته كمكرر.
تعمل الوظيفة عن طريق إدخال نقطتين إلى المسار للتأكد من أن جميع الإدخالات تحتوي على نقطتين في المقدمة ، ثم يتم إدخال الإدخال الجديد على المسار الحالي مع إزالة هذا الإدخال. يتم تنفيذ الجزء الأخير باستخدام ترميز bash's ${var//pattern/sub}
؛ راجع دليل bash لمزيد من التفاصيل.
بالنسبة إلى الدفع المسبق ، يعجبني حل @ 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
}
إليكم (أعتقد أنه كتب منذ سنوات من قبل أوسكار ، مسؤول النظام في مختبري القديم ، وكل الفضل في ذلك) ، كان موجودًا في bashrc منذ أمد بعيد. له فائدة إضافية تتمثل في السماح لك بإلحاق الدليل الجديد أو إلحاقه حسب الرغبة:
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/
اسم مستعار بسيط مثل هذا أدناه يجب أن تفعل الخدعة:
alias checkInPath="echo $PATH | tr ':' '\n' | grep -x -c "
كل ما تفعله هو تقسيم المسار على: character ومقارنة كل مكون مقابل الوسيطة التي تمر بها. يتحقق 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 أو بعض الاسم المستعار/الوظيفة المماثلة.
إليكم ما أصبت به:
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
}
راجع كيف تحافظ على تكرار متغير المسار في csh؟ في StackOverflow للحصول على مجموعة واحدة من الإجابات على هذا السؤال.
أنا مندهش بعض الشيء لأنه لم يشر أحد إلى هذا بعد ، لكن يمكنك استخدام readlink -f
لتحويل المسارات النسبية إلى مسارات مطلقة ، وإضافتها إلى PATH على هذا النحو.
على سبيل المثال ، لتحسين إجابة Guillaume Perrault-Archambault ،
pathappend() {
for ARG in "[email protected]"
do
if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
PATH="${PATH:+"$PATH:"}$ARG"
fi
done
}
يصبح
pathappend() {
for ARG in "[email protected]"
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
}
سيقوم الأمر readlink -f
(من بين أشياء أخرى) بتحويل مسار نسبي إلى مسار مطلق. هذا يسمح لك أن تفعل شيئا مثل
$ cd /path/to/my/bin/dir Budap.____. News$ pathappend . $ صدى "$ PATH" <your_old_path>:/الطريق// لي/بن/دير
حسنا ، النظر في المثال أعلاه. إذا قال المستخدم pathappend .
من دليل /path/to/my/bin/dir
للمرة الثانية ، فسيكون ARG
.
. بالطبع ، .
لن تكون موجودة في PATH
. ولكن بعد ذلك ، سيتم تعيين ARGA
على /path/to/my/bin/dir
(مكافئ المسار المطلق لـ .
) ، والذيهوبالفعل في PATH
. لذلك نحن بحاجة إلى تجنب إضافة /path/to/my/bin/dir
إلى PATH
مرة ثانية.
ربما الأهم من ذلك ، أن الغرض الأساسي من 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
، فأنت لديك بالفعل /usr/lib/Perl/5.14.2
في PATH ، ومرة أخرى ، نحن بحاجة إلى التحقق من ذلك لتجنب إضافته إلى PATH
مرة ثانيه.
if ARGA=$(readlink -f "$ARG")
؟إذا كان الأمر غير واضح ، فإن هذا السطر يختبر مدى نجاح readlink
. هذا هو مجرد ممارسة البرمجة الدفاعية جيدة. إذا كنا سنستخدم الإخراج من commandmas جزء من commandn(حيثm<n) ، من الحكمة التحقق مما إذا كان الأمرmفشل والتعامل مع ذلك بطريقة ما. لا أعتقد أنه من المحتمل أن readlink
ستفشل - ولكن ، كما تمت مناقشته في كيفية استرداد المسار المطلق لملف تعسفي من OSX وفي أي مكان آخر ، readlink
هو GNU اختراع. لم يتم تحديده في POSIX ، لذا فإن توفره في Mac OS و Solaris و Unixes غير التابعة لنظام Linux أمر مشكوك فيه. من خلال تثبيت شبكة أمان ، نجعل كودنا أكثر قابلية للتنقل إلى حد ما.
بالطبع ، إذا كنت تستخدم نظامًا لا يملك readlink
، فلن ترغب في القيام بـ pathappend .
.
ربما يكون اختبار -d
الثاني ([ -d "$ARGA" ]
) غير ضروري على الأرجح. لا يمكنني التفكير في أي سيناريو حيث $ARG
هو دليل ونجاح readlink
، لكن $ARGA
ليس دليلًا. لقد قمت فقط بنسخ ولصق أول عبارة if
لإنشاء العبارة الثالثة ، وتركت اختبار -d
هناك بدافع الكسل.
بلى. مثل العديد من الإجابات الأخرى هنا ، يختبر هذا السؤال ما إذا كانت كل وسيطة عبارة عن دليل ، ومعالجتها إن كانت موجودة ، وتجاهلها إن لم تكن كذلك. قد يكون هذا (أو لا) مناسبًا إذا كنت تستخدم pathappend
فقط في ملفات (.
) (مثل .bash_profile
و .bashrc
) والبرامج النصية الأخرى. ولكن ، كما أظهرت هذه الإجابة (أعلاه) ، من الممكن تمامًا استخدامها بشكل تفاعلي. سوف تكون في حيرة جدا إذا كنت تفعل
$ pathappend /usr/local/nysql/bin
$ mysql
-bash: mysql: command not found
هل لاحظت أنني قلت
nysql
في أمرpathappend
، بدلاً منmysql
؟ وهذاpathappend
لم يقل شيئًا ؛ انها مجرد تجاهل بصمت حجة غير صحيحة؟
كما قلت أعلاه ، من الممارسات الجيدة التعامل مع الأخطاء. إليك مثال على ذلك:
pathappend() {
for ARG in "[email protected]"
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
}
هذه الطريقة تعمل بشكل جيد:
if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
function __path_add(){
if [ -d "$1" ] ; then
local D=":${PATH}:";
[ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
fi
}
إصداراتي أقل حرصًا على المسارات الفارغة والإصرار على أن تكون المسارات صالحة والدلائل من بعض المنشورات هنا ، لكنني أجد مجموعة كبيرة من الإضافات/الإضافات/النظيفة/الفريدة من نوعها/الخ. وظائف Shell لتكون مفيدة لمعالجة المسار. المجموعة بأكملها ، في حالتها الحالية ، هنا: http://Pastebin.com/xS9sgQsX (التعليقات والتحسينات موضع ترحيب كبير!)
إليك طريقة متوافقة مع 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 "[email protected]")"
}
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 "[email protected]"; 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: علة ثابتة لكل سكوت
يتيح لك هذا البرنامج النصي الإضافة في نهاية $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
}
يمكنك التحقق مما إذا كان قد تم تعيين متغير مخصص ، أو قم بضبطه ، ثم أضف الإدخالات الجديدة:
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
.
يمكنك استخدام بطانة بيرل واحدة:
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;' "[email protected]"
}
ومن هنا في باش:
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
}
لقد عدلت قليلاً إجابة جوردون دافيسون لاستخدام دير الحالي إذا لم يتم توفير أي شيء. بحيث يمكنك فقط القيام بـ padd
من الدليل الذي ترغب في إضافته إلى PATH.
padd() {
current=`pwd`
p=${1:-$current}
if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
PATH="$p:$PATH"
fi
}