it-swarm.asia

أضف دليلًا إلى PATH $ إذا لم يكن موجودًا بالفعل

هل قام أي شخص بكتابة دالة 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() والتي تضيف الدليل فقط إذا لم يكن هناك. ولكن ، إذا كان أي شخص قد كتب بالفعل (أو وجد) مثل هذا الشيء ، فلن أقضي الوقت في ذلك.

125
Doug Harris

من بلدي .bashrc:

pathadd() {
    if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
        PATH="${PATH:+"$PATH:"}$1"
    fi
}

لاحظ أنه يجب وضع علامة على PATH بالفعل على أنها تصدير ، لذلك ليست هناك حاجة لإعادة التصدير. يؤدي هذا إلى التحقق مما إذا كان الدليل موجودًا وهو دليل قبل إضافته ، وهو أمر قد لا يهمك.

هذا أيضًا يضيف الدليل الجديد إلى نهاية المسار ؛ لوضعه في البداية ، استخدم PATH="$1${PATH:+":$PATH"}" بدلاً من السطر PATH= أعلاه.

123
Gordon Davisson

التوسع في إجابة جوردون دافيسون ، وهذا يدعم حجج متعددة

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 ...

19
Guillaume Perrault-Archambault

إليك شيء من إجابتي إلى هذا السؤال مقترنًا بهيكل دوغ وظيفة هاريس. يستخدم تعبيرات Bash العادية:

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}
12
Dennis Williamson

ضع هذا في التعليقات على الإجابة المحددة ، ولكن لا يبدو أن التعليقات تدعم تنسيق PRE ، لذلك أضف الإجابة هنا:

@ gordon-davisson أنا لست معجبًا كبيرًا بالاقتباس والتسلسل غير الضروريين. على افتراض أنك تستخدم إصدار bash> = 3 ، يمكنك بدلاً من ذلك استخدام bash المضمنة في regexs والقيام بما يلي:

pathadd() {
    if [ -d "$1" ] && [[ ! $PATH =~ (^|:)$1(:|$) ]]; then
        PATH+=:$1
    fi
}

هذا يعالج بشكل صحيح الحالات حيث توجد مسافات في الدليل أو PATH. هناك بعض التساؤلات حول ما إذا كان محرك bash المدمج في محرك regex بطيئًا بدرجة كافية بحيث يكون هذا الشبكة أقل كفاءة من تسلسل السلسلة والاستيفاء الذي تقوم به نسختك ، لكن بطريقة ما تشعر أنها أكثر نظافة جماليًا بالنسبة لي.

10
Christopher Smith
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

إليك حل بديل يتمتع بميزة إضافية تتمثل في إزالة التكرار الزائد:

function pathadd {
    PATH=:$PATH
    PATH=$1${PATH//:$1/}
}

الوسيطة الفردية لهذه الوظيفة يتم تعليقها مسبقًا على PATH ، وتتم إزالة المثيل الأول من نفس السلسلة من المسار الموجود. بمعنى آخر ، إذا كان الدليل موجودًا بالفعل في المسار ، فسيتم ترقيته إلى المقدمة بدلاً من إضافته كمكرر.

تعمل الوظيفة عن طريق إدخال نقطتين إلى المسار للتأكد من أن جميع الإدخالات تحتوي على نقطتين في المقدمة ، ثم يتم إدخال الإدخال الجديد على المسار الحالي مع إزالة هذا الإدخال. يتم تنفيذ الجزء الأخير باستخدام ترميز bash's ${var//pattern/sub}؛ راجع دليل bash لمزيد من التفاصيل.

6
Rob Hague

بالنسبة إلى الدفع المسبق ، يعجبني حل @ 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
}
5
PeterS6g

إليكم (أعتقد أنه كتب منذ سنوات من قبل أوسكار ، مسؤول النظام في مختبري القديم ، وكل الفضل في ذلك) ، كان موجودًا في 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/
5
terdon

اسم مستعار بسيط مثل هذا أدناه يجب أن تفعل الخدعة:

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 أو بعض الاسم المستعار/الوظيفة المماثلة.

4
nagul

إليكم ما أصبت به:

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

راجع كيف تحافظ على تكرار متغير المسار في csh؟ في StackOverflow للحصول على مجموعة واحدة من الإجابات على هذا السؤال.

2
Jonathan Leffler

أنا مندهش بعض الشيء لأنه لم يشر أحد إلى هذا بعد ، لكن يمكنك استخدام 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
}

1. الأساسيات - ما فائدة هذا؟

سيقوم الأمر readlink -f (من بين أشياء أخرى) بتحويل مسار نسبي إلى مسار مطلق. هذا يسمح لك أن تفعل شيئا مثل

$ cd /path/to/my/bin/dir Budap.____. News$ pathappend .
 $ صدى "$ PATH" 
<your_old_path>:/الطريق// لي/بن/دير

2. لماذا نحن اختبار لكونه في 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 مرة ثانيه.

3. ما هي الصفقة مع 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 هناك بدافع الكسل.

4. أي تعليقات أخرى؟

بلى. مثل العديد من الإجابات الأخرى هنا ، يختبر هذا السؤال ما إذا كانت كل وسيطة عبارة عن دليل ، ومعالجتها إن كانت موجودة ، وتجاهلها إن لم تكن كذلك. قد يكون هذا (أو لا) مناسبًا إذا كنت تستخدم 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
}
2
qwertyzw

هذه الطريقة تعمل بشكل جيد:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
1
user2425755
function __path_add(){  
if [ -d "$1" ] ; then  
    local D=":${PATH}:";   
    [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  
1
GreenFox

إصداراتي أقل حرصًا على المسارات الفارغة والإصرار على أن تكون المسارات صالحة والدلائل من بعض المنشورات هنا ، لكنني أجد مجموعة كبيرة من الإضافات/الإضافات/النظيفة/الفريدة من نوعها/الخ. وظائف Shell لتكون مفيدة لمعالجة المسار. المجموعة بأكملها ، في حالتها الحالية ، هنا: http://Pastebin.com/xS9sgQsX (التعليقات والتحسينات موضع ترحيب كبير!)

0
andybuckley

إليك طريقة متوافقة مع 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: علة ثابتة لكل سكوت

0
go2null

يتيح لك هذا البرنامج النصي الإضافة في نهاية $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

يمكنك التحقق مما إذا كان قد تم تعيين متغير مخصص ، أو قم بضبطه ، ثم أضف الإدخالات الجديدة:

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
David Kennedy

يمكنك استخدام بطانة بيرل واحدة:

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
}
0
dpatru

لقد عدلت قليلاً إجابة جوردون دافيسون لاستخدام دير الحالي إذا لم يتم توفير أي شيء. بحيث يمكنك فقط القيام بـ padd من الدليل الذي ترغب في إضافته إلى PATH.

padd() {
  current=`pwd`
  p=${1:-$current}
  if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
      PATH="$p:$PATH"
  fi
}
0
Thorsten Lorenz