bash - جعل اسم مستعار باش يأخذ معلمة؟




alias (8)

اعتدت على استخدام CShell ( csh ) ، والذي يتيح لك إنشاء اسم مستعار يأخذ معلمة. كان الترميز شيء من هذا القبيل

alias junk="mv \\!* ~/.Trash"

في باش ، يبدو أن هذا لا يعمل. وبالنظر إلى أن باش لديه العديد من الميزات المفيدة ، فإنني أفترض أن هذا البرنامج قد تم تنفيذه ولكني أتساءل كيف.


TL ؛ DR: هل هذا بدلا من ذلك

أسهل بكثير وأكثر قابلية للقراءة لاستخدام وظيفة من اسم مستعار لوضع الحجج في منتصف الأمر.

$ wrap_args() { echo "before [email protected] after"; }
$ wrap_args 1 2 3
before 1 2 3 after

إذا كنت تقرأ ، فستتعلم أشياء لا تحتاج إلى معرفتها حول معالجة معاملات shell. المعرفة خطرة. فقط الحصول على النتيجة التي تريدها ، قبل أن يتحكم الجانب المظلم إلى الأبد مصيرك.

توضيح

تقبل الأسماء المستعارة لـ bash الحجج ، ولكن في النهاية فقط:

$ alias speak=echo
$ speak hello world
hello world

إن وضع الحجج في وسط القيادة عبر alias أمر ممكن بالفعل ولكنه يصبح قبيحًا.

لا تجرب هذا في المنزل ، يا رفاق!

إذا كنت تحب التحايل على القيود وفعل ما يقوله الآخرون مستحيلاً ، فإليك الوصفة. فقط لا تلومني إذا شعر شعرك بالارتباك ووجهك مغطى بكوكب السخام.

الحل هو تمرير الوسيطات التي يقبلها alias فقط في نهاية الالتفاف الذي سيتم إدراجها في الوسط ثم تنفيذ الأمر الخاص بك.

الحل 1

إذا كنت حقا ضد استخدام وظيفة في حد ذاتها ، يمكنك استخدام:

$ alias wrap_args='f(){ echo before "[email protected]" after;  unset -f f; }; f'
$ wrap_args x y z
before x y z after

يمكنك استبدال [email protected] $1 إذا كنت تريد الوسيطة الأولى فقط.

شرح 1

يؤدي هذا إلى إنشاء دالة مؤقتة f ، والتي يتم تمريرها في الوسيطات (لاحظ أنه يتم استدعاء f في النهاية). يزيل unset -f تعريف الدالة كما يتم تنفيذ الاسم المستعار بحيث لا يتدلى بعد ذلك.

الحل 2

يمكنك أيضا استخدام subshell:

$ alias wrap_args='sh -c '\''echo before "[email protected]" after'\'' _'

شرح 2

ينشئ الاسم المستعار أمرًا مثل:

sh -c 'echo before "[email protected]" after' _

تعليقات:

  • العنصر النائب مطلوب ، ولكن يمكن أن يكون أي شيء. يتم تعيينه على sh $0 s $0 ، وهو مطلوب بحيث لا يتم استهلاك أول من الوسائط التي قدمها المستخدم. برهنة:

    sh -c 'echo Consumed: "$0" Printing: "[email protected]"' alcohol drunken babble
    Consumed: alcohol Printing: drunken babble
    
  • مطلوب الاقتباس المفرد داخل الاقتباس المفرد. في ما يلي مثال على أنه لا يعمل مع علامات اقتباس مزدوجة:

    $ sh -c "echo Consumed: $0 Printing: [email protected]" alcohol drunken babble
    Consumed: -bash Printing:
    

    هنا يتم استبدال قيم $0 و [email protected] في القشرة التفاعلية بـ الـ double المشار قبل أن يتم تمريرها إلى sh . هنا دليل:

    echo "Consumed: $0 Printing: [email protected]"
    Consumed: -bash Printing:
    

    تضمن علامات الاقتباس المفردة عدم تفسير هذه المتغيرات بواسطة غلاف تفاعلي ، ويتم تمريرها حرفياً إلى sh -c .

    يمكنك استخدام علامات الاقتباس المزدوجة و \[email protected] ، ولكن أفضل الممارسات هي اقتباس حججك (لأنها قد تحتوي على مسافات) ، و \"\[email protected]\" تبدو أكثر قبحًا ، ولكن قد تساعدك على الفوز بمسابقة تشويش حيث الشعر المنهار هو شرط أساسي للدخول.


إذا كنت تبحث عن طريقة عامة لتطبيق جميع المعلمات على إحدى الوظائف ، وليس مجرد واحد أو اثنين أو أكثر من الكمية الأخرى ، فيمكنك القيام بذلك بهذه الطريقة:

#!/usr/bin/env bash

# you would want to `source` this file, maybe in your .bash_profile?
function runjar_fn(){
    java -jar myjar.jar "[email protected]";
}

alias runjar=runjar_fn;

لذا في المثال أعلاه ، runjar جميع المعلمات من عند تشغيل runjar إلى الاسم المستعار.

على سبيل المثال ، إذا فعلت runjar hi there نهاية المطاف تشغيل java -jar myjar.jar hi there . إذا فعلت runjar one two three أنها تشغيل java -jar myjar.jar one two three .

أنا أحب هذا الحل القائم على [email protected] لأنه يعمل مع أي عدد من المعلمات.


الوظائف هي في الواقع الإجابة دائمًا كما ساهمت بالفعل وبإسراع من هذا الاقتباس من صفحة الرجل: "بالنسبة إلى كل الأغراض تقريبًا ، يتم استبدال الأسماء المستعارة بوظائف shell."

للتأكد من اكتمالها ولأن هذا يمكن أن يكون مفيدًا (بناء جملة خفيف الوزن بشكل هامشي) ، يمكن ملاحظة أنه عندما تتبع المعلمة (المعلمات) الاسم المستعار ، يمكن الاستمرار في استخدامها (على الرغم من أن هذا لا يعالج متطلبات OP). ربما يكون من الأسهل إثبات ذلك بمثال:

alias ssh_disc='ssh -O stop'

يسمح لي أن اكتب smth مثل ssh_disc myhost ، والتي يتم توسيعها كما هو متوقع كما يلي: ssh -O stop myhost

هذا يمكن أن يكون مفيدا للأوامر التي تأخذ الحجج المعقدة (ذاكرتي ليست ما تستخدمه بعد الآن ...)


في ما يلي ثلاثة أمثلة على الوظائف التي ~/.bashrc في ~/.bashrc ، وهي في الأساس أسماء مستعارة تقبل معلمة:

#Utility required by all below functions.
#https://.com/questions/369758/how-to-trim-whitespace-from-bash-variable#comment21953456_3232433
alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"

.

:<<COMMENT
    Alias function for recursive deletion, with are-you-sure prompt.

    Example:
        srf /home/myusername/django_files/rest_tutorial/rest_venv/

    Parameter is required, and must be at least one non-whitespace character.

    Short description: Stored in SRF_DESC

    With the following setting, this is *not* added to the history:
        export HISTIGNORE="*rm -r*:srf *"
    - https://superuser.com/questions/232885/can-you-share-wisdom-on-using-histignore-in-bash

    See:
    - y/n prompt: https://.com/a/3232082/2736496
    - Alias w/param: https://.com/a/7131683/2736496
COMMENT
#SRF_DESC: For "aliaf" command (with an 'f'). Must end with a newline.
SRF_DESC="srf [path]: Recursive deletion, with y/n prompt\n"
srf()  {
    #Exit if no parameter is provided (if it's the empty string)
        param=$(echo "$1" | trim)
        echo "$param"
        if [ -z "$param" ]  #http://tldp.org/LDP/abs/html/comparison-ops.html
        then
          echo "Required parameter missing. Cancelled"; return
        fi

    #Actual line-breaks required in order to expand the variable.
    #- https://.com/a/4296147/2736496
    read -r -p "About to
    sudo rm -rf \"$param\"
Are you sure? [y/N] " response
    response=${response,,}    # tolower
    if [[ $response =~ ^(yes|y)$ ]]
    then
        sudo rm -rf "$param"
    else
        echo "Cancelled."
    fi
}

.

:<<COMMENT
    Delete item from history based on its line number. No prompt.

    Short description: Stored in HX_DESC

    Examples
        hx 112
        hx 3

    See:
    - https://unix.stackexchange.com/questions/57924/how-to-delete-commands-in-history-matching-a-given-string
COMMENT
#HX_DESC: For "aliaf" command (with an 'f'). Must end with a newline.
HX_DESC="hx [linenum]: Delete history item at line number\n"
hx()  {
    history -d "$1"
}

.

:<<COMMENT
    Deletes all lines from the history that match a search string, with a
    prompt. The history file is then reloaded into memory.

    Short description: Stored in HXF_DESC

    Examples
        hxf "rm -rf"
        hxf ^source

    Parameter is required, and must be at least one non-whitespace character.

    With the following setting, this is *not* added to the history:
        export HISTIGNORE="*hxf *"
    - https://superuser.com/questions/232885/can-you-share-wisdom-on-using-histignore-in-bash

    See:
    - https://unix.stackexchange.com/questions/57924/how-to-delete-commands-in-history-matching-a-given-string
COMMENT
#HXF_DESC: For "aliaf" command (with an 'f'). Must end with a newline.
HXF_DESC="hxf [searchterm]: Delete all history items matching search term, with y/n prompt\n"
hxf()  {
    #Exit if no parameter is provided (if it's the empty string)
        param=$(echo "$1" | trim)
        echo "$param"
        if [ -z "$param" ]  #http://tldp.org/LDP/abs/html/comparison-ops.html
        then
          echo "Required parameter missing. Cancelled"; return
        fi

    read -r -p "About to delete all items from history that match \"$param\". Are you sure? [y/N] " response
    response=${response,,}    # tolower
    if [[ $response =~ ^(yes|y)$ ]]
    then
        #Delete all matched items from the file, and duplicate it to a temp
        #location.
        grep -v "$param" "$HISTFILE" > /tmp/history

        #Clear all items in the current sessions history (in memory). This
        #empties out $HISTFILE.
        history -c

        #Overwrite the actual history file with the temp one.
        mv /tmp/history "$HISTFILE"

        #Now reload it.
        history -r "$HISTFILE"     #Alternative: exec bash
    else
        echo "Cancelled."
    fi
}

المراجع:


لا يقبل اسم مستعار باش المعلمات مباشرة. سيكون لديك لإنشاء وظيفة واسم مستعار ذلك.

لا يقبل alias المعلمات ولكن يمكن استدعاء دالة مثل اسم مستعار تمامًا. فمثلا:

myfunction() {
    #do things with parameters like $1 such as
    mv "$1" "$1.bak"
    cp "$2" "$1"
}


myFunction xyz #calls `myfunction`

بالمناسبة ، فإن وظائف Bash المحددة في .bashrc والملفات الأخرى متوفرة .bashrc داخل shell الخاص بك. على سبيل المثال ، يمكنك استدعاء الوظيفة السابقة مثل هذا

$ myfunction original.conf my.conf

ملحوظة: في حالة عدم وضوح الفكرة ، من الخطأ استخدام الأسماء المستعارة لأي شيء سوى الأسماء المستعارة ، أولها "الوظيفة في اسم مستعار" والثاني هو "من الصعب قراءة إعادة التوجيه / المصدر". أيضا ، هناك عيوب (التي اعتقدت أنها ستكون واضحة ، ولكن فقط في حالة الخلط: لا أقصد استخدامها فعليًا ... في أي مكان!)

.................................................. .................................................. ............................................

لقد أجبت على هذا من قبل ، وكانت دائمًا هكذا في الماضي:

alias foo='__foo() { unset -f $0; echo "arg1 for foo=$1"; }; __foo()'

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

alias bar='cat <<< '\''echo arg1 for bar=$1'\'' | source /dev/stdin'

هم على حد سواء عن نفس طول تعطي أو تأخذ بعض الأحرف.

والراكب الحقيقي هو فارق التوقيت ، والجزء العلوي هو "أسلوب الوظيفة" والقاع هو أسلوب "إعادة توجيه المصدر". لإثبات هذه النظرية ، فإن التوقيت يتحدث عن نفسه:

arg1 for foo=FOOVALUE
 real 0m0.011s user 0m0.004s sys 0m0.008s  # <--time spent in foo
 real 0m0.000s user 0m0.000s sys 0m0.000s  # <--time spent in bar
arg1 for bar=BARVALUE
[email protected] /usr/bin# time foo FOOVALUE; time bar BARVALUE
arg1 for foo=FOOVALUE
 real 0m0.010s user 0m0.004s sys 0m0.004s
 real 0m0.000s user 0m0.000s sys 0m0.000s
arg1 for bar=BARVALUE
[email protected] /usr/bin# time foo FOOVALUE; time bar BARVALUE
arg1 for foo=FOOVALUE
 real 0m0.011s user 0m0.000s sys 0m0.012s
 real 0m0.000s user 0m0.000s sys 0m0.000s
arg1 for bar=BARVALUE
[email protected] /usr/bin# time foo FOOVALUE; time bar BARVALUE
arg1 for foo=FOOVALUE
 real 0m0.012s user 0m0.004s sys 0m0.004s
 real 0m0.000s user 0m0.000s sys 0m0.000s
arg1 for bar=BARVALUE
[email protected] /usr/bin# time foo FOOVALUE; time bar BARVALUE
arg1 for foo=FOOVALUE
 real 0m0.010s user 0m0.008s sys 0m0.004s
 real 0m0.000s user 0m0.000s sys 0m0.000s
arg1 for bar=BARVALUE

هذا هو الجزء السفلي من حوالي 200 نتيجة ، تتم على فترات عشوائية. يبدو أن إنشاء / إتاحة الوظائف يستغرق وقتًا أطول من إعادة التوجيه. نأمل أن يساعد هذا الزائرين في المستقبل على هذا السؤال (لا يريدون الاحتفاظ به لنفسي).


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

ما أجبرني في الآونة الأخيرة على محاولة إيجاد حل لذلك هو أنني أردت إنشاء بعض الأوامر المختصرة لطباعة تعريفات المتغيرات والوظائف. لذلك كتبت بعض الوظائف لهذا الغرض. ومع ذلك ، هناك بعض المتغيرات التي (أو قد تتغير) من قبل استدعاء وظيفة نفسها. من بين هؤلاء:

FUNCNAME BASH_SOURCE BASH_LINENO BASH_ARGC BASH_ARGV

الأمر الأساسي الذي كنت أستخدمه (في إحدى الوظائف) لطباعة المتغير defns. في نموذج الإخراج بواسطة الأمر set كان:

sv () { set | grep --color=never -- "^$1=.*"; }

على سبيل المثال:

> V=voodoo
sv V
V=voodoo

المشكلة: لن يقوم هذا بطباعة تعريفات المتغيرات المذكورة أعلاه كما هي في السياق الحالي ، على سبيل المثال ، إذا لم يكن تعريف FUNCNAME محددًا في مطالبة shell التفاعلية (أو لا في أي من مكالمات الدالة). لكن وظيفتي تخبرني بالمعلومات الخاطئة:

> sv FUNCNAME
FUNCNAME=([0]="sv")

لقد ذكر أحد الحلول التي توصلت إليها من قبل الآخرين في مشاركات أخرى حول هذا الموضوع. لهذا الأمر المحدد لطباعة متغير defns. ، والذي يتطلب وسيطة واحدة فقط ، فعلت هذا:

alias asv='(grep -- "^$(cat -)=.*" <(set)) <<<'

الذي يعطي الإخراج الصحيح (لا شيء) ، وحالة النتيجة (خطأ):

> asv FUNCNAME
> echo $?
1

ومع ذلك ، كنت لا أزال أرغب في إيجاد حل يعمل من أجل عدد من الحجج العشوائية.

حل عام لتمرير الحجج التعسفية لقيادة باش ألياسيد:

# (I put this code in a file "alias-arg.sh"):

# cmd [arg1 ...] – an experimental command that optionally takes args,
# which are printed as "cmd(arg1 ...)"
#
# Also sets global variable "CMD_DONE" to "true".
#
cmd () { echo "cmd([email protected])"; declare -g CMD_DONE=true; }

# Now set up an alias "ac2" that passes to cmd two arguments placed
# after the alias, but passes them to cmd with their order reversed:
#
# ac2 cmd_arg2 cmd_arg1 – calls "cmd" as: "cmd cmd_arg1 cmd_arg2"
#
alias ac2='
    # Set up cmd to be execed after f() finishes:
    #
    trap '\''cmd "${CMD_ARGV[1]}" "${CMD_ARGV[0]}"'\'' SIGUSR1;
    #        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    #       (^This is the actually execed command^)
    #
    # f [arg0 arg1 ...] – acquires args and sets up trap to run cmd:
    f () {
        declare -ag CMD_ARGV=("[email protected]");  # array to give args to cmd
        kill -SIGUSR1 $$;             # this causes cmd to be run
        trap SIGUSR1;                 # unset the trap for SIGUSR1
        unset CMD_ARGV;               # clean up env...
        unset f;                      # incl. this function!
    };
    f'  # Finally, exec f, which will receive the args following "ac2".

على سبيل المثال:

> . alias-arg.sh
> ac2 one two
cmd(two one)
>
> # Check to see that command run via trap affects this environment:
> asv CMD_DONE
CMD_DONE=true

والشيء الجميل في هذا الحل هو أن كل الحيل الخاصة المستخدمة للتعامل مع المعلمات الموضعية (الحجج) للأوامر سوف تعمل عند إنشاء الأمر المحبوس. والفرق الوحيد هو أنه يجب استخدام بنية الصفيف.

على سبيل المثال،

إذا كنت تريد "$ @" ، فاستخدم "$ {CMD_ARGV [@]}".

إذا كنت تريد "$ #" ، فاستخدم "$ {# CMD_ARGV [@]}".

إلخ.


ومع ذلك ، لا تحتاج إلى وظائف عند إنشاء الأسماء المستعارة في ملف .bashrc . فمثلا

# create an alias that takes port-number by the user
alias serve="python -m SimpleHTTPServer $1"

بعد إجراء التغيير في ملف .bashrc ، تأكد من إدخال الأمر التالي.

~$ source .bashrc

يجب أن تكون قادرًا على استخدام هذا مثل ذلك

~$ serve 8998




alias