linux - tutorial - shell script شرح




كيف أطلب إدخال "نعم/لا"/"إلغاء" في برنامج نصي من نوع Linux Linux؟ (17)

أرغب في إيقاف الإدخال مؤقتًا في نص برمجي shell ، ومطالبة المستخدم باختياراته. معيار النوع "نعم ، لا ، أو إلغاء". كيف يمكنني تحقيق ذلك في رسالة bash النموذجية؟


خمسة إجابات على الأقل لسؤال عام واحد.

اعتمادا علي

  • متوافق مع posix : يمكن أن يعمل على أنظمة ضعيفة مع بيئات shell العامة
  • bash محددة: استخدام ما يسمى bashisms

وايضا لو اردت

  • بسيط "في الخط" 'سؤال / إجابة (حلول عامة)
  • واجهات مهيأة جدا ، مثل ncurses أو أكثر رسومية باستخدام libgtk أو libqt ...
  • استخدام قدرة قوية التاريخ التاريخ

1. POSIX حلول عامة

يمكنك استخدام الأمر " read متبوعًا بـ " if ... then ... else

echo -n "Is this a good question (y/n)? "
read answer

# if echo "$answer" | grep -iq "^y" ;then

# (شكرا لتعليق آدم كاتز : هذا الاختبار هو أكثر المحمولة وتجنب شوكة واحدة :)

if [ "$answer" != "${answer#[Yy]}" ] ;then
    echo Yes
else
    echo No
fi

POSIX ، ولكن ميزة رئيسية واحدة

ولكن إذا كنت لا تريد أن يضطر المستخدم إلى الوصول إلى " العودة" ، فيمكنك كتابة ما يلي:

( تم تعديله : كما يقترح جوناثان لوفلر بشكل صحيح ، يمكن أن يكون توفير إعدادات stty أفضل من مجرد إجبارهم على العقل ).

echo -n "Is this a good question (y/n)? "
old_stty_cfg=$(stty -g)
stty raw -echo ; answer=$(head -c 1) ; stty $old_stty_cfg # Careful playing with stty
if echo "$answer" | grep -iq "^y" ;then
    echo Yes
else
    echo No
fi

ملاحظة: تم اختبار هذا تحت sh ، bash ، ksh ، dash و busybox !

نفس الشيء ، ولكن في انتظار صريح أو ص :

#/bin/sh
echo -n "Is this a good question (y/n)? "
old_stty_cfg=$(stty -g)
stty raw -echo
answer=$( while ! head -c 1 | grep -i '[ny]' ;do true ;done )
stty $old_stty_cfg
if echo "$answer" | grep -iq "^y" ;then
    echo Yes
else
    echo No
fi

باستخدام أدوات مخصصة

هناك العديد من الأدوات التي بنيت باستخدام libncurses ، libgtk ، libqt أو مكتبات رسومية أخرى. على سبيل المثال ، باستخدام whiptail :

if whiptail --yesno "Is this a good question" 20 60 ;then
    echo Yes
else
    echo No
fi

اعتمادًا على نظامك ، قد تحتاج إلى استبدال whiptail بأداة مماثلة أخرى:

dialog --yesno "Is this a good question" 20 60 && echo Yes

gdialog --yesno "Is this a good question" 20 60 && echo Yes

kdialog --yesno "Is this a good question" 20 60 && echo Yes

حيث 20 ارتفاع مربع الحوار في عدد الأسطر و 60 عرض عرض مربع الحوار. هذه الأدوات جميعها قريبة من نفس التركيب.

DIALOG=whiptail
if [ -x /usr/bin/gdialog ] ;then DIALOG=gdialog ; fi
if [ -x /usr/bin/xdialog ] ;then DIALOG=xdialog ; fi
...
$DIALOG --yesno ...

2. باش حلول محددة

الطريقة الأساسية في الخط

read -p "Is this a good question (y/n)? " answer
case ${answer:0:1} in
    y|Y )
        echo Yes
    ;;
    * )
        echo No
    ;;
esac

أفضل استخدام case حتى أتمكن من اختبار yes | ja | si | oui yes | ja | si | oui yes | ja | si | oui إذا لزم الأمر ...

تمشيا مع ميزة رئيسية واحدة

ضمن bash ، يمكننا تحديد طول الإدخال المقصود لأمر read :

read -n 1 -p "Is this a good question (y/n)? " answer

ضمن bash ، يقبل الأمر " read " معلمة timeout ، والتي قد تكون مفيدة.

read -t 3 -n 1 -p "Is this a good question (y/n)? " answer
[ -z "$answer" ] && answer="Yes"  # if 'yes' have to be default choice

بعض الحيل للأدوات المخصصة

مربعات حوار أكثر تعقيدًا ، تتجاوز yes - no البسيطة yes - no أغراض:

dialog --menu "Is this a good question" 20 60 12 y Yes n No m Maybe

شريط التقدم:

dialog --gauge "Filling the tank" 20 60 0 < <(
    for i in {1..100};do
        printf "XXX\n%d\n%(%a %b %T)T progress: %d\nXXX\n" $i -1 $i
        sleep .033
    done
) 

عرض صغير:

#!/bin/sh
while true ;do
    [ -x "$(which ${DIALOG%% *})" ] || DIALOG=dialog
    DIALOG=$($DIALOG --menu "Which tool for next run?" 20 60 12 2>&1 \
            whiptail       "dialog boxes from shell scripts" >/dev/tty \
            dialog         "dialog boxes from shell with ncurses" \
            gdialog        "dialog boxes from shell with Gtk" \
            kdialog        "dialog boxes from shell with Kde" ) || exit
    clear;echo "Choosed: $DIALOG."
    for i in `seq 1 100`;do
        date +"`printf "XXX\n%d\n%%a %%b %%T progress: %d\nXXX\n" $i $i`"
        sleep .0125
      done | $DIALOG --gauge "Filling the tank" 20 60 0
    $DIALOG --infobox "This is a simple info box\n\nNo action required" 20 60
    sleep 3
    if $DIALOG --yesno  "Do you like this demo?" 20 60 ;then
        AnsYesNo=Yes; else AnsYesNo=No; fi
    AnsInput=$($DIALOG --inputbox "A text:" 20 60 "Text here..." 2>&1 >/dev/tty)
    AnsPass=$($DIALOG --passwordbox "A secret:" 20 60 "First..." 2>&1 >/dev/tty)
    $DIALOG --textbox /etc/motd 20 60
    AnsCkLst=$($DIALOG --checklist "Check some..." 20 60 12 \
        Correct "This demo is useful"        off \
        Fun        "This demo is nice"        off \
        Strong        "This demo is complex"        on 2>&1 >/dev/tty)
    AnsRadio=$($DIALOG --radiolist "I will:" 20 60 12 \
        " -1" "Downgrade this answer"        off \
        "  0" "Not do anything"                on \
        " +1" "Upgrade this anser"        off 2>&1 >/dev/tty)
    out="Your answers:\nLike: $AnsYesNo\nInput: $AnsInput\nSecret: $AnsPass"
    $DIALOG --msgbox "$out\nAttribs: $AnsCkLst\nNote: $AnsRadio" 20 60
  done

5. استخدام تاريخ readline

مثال:

#!/bin/bash

set -i
HISTFILE=~/.myscript.history
history -c
history -r

myread() {
    read -e -p '> ' $1
    history -s ${!1}
}
trap 'history -a;exit' 0 1 2 3 6

while myread line;do
    case ${line%% *} in
        exit )  break ;;
        *    )  echo "Doing something with '$line'" ;;
      esac
  done

سيؤدي ذلك إلى إنشاء ملف .myscript.history في دليل $HOME الخاص بك ، بدلاً من استخدام أوامر محفوظات القراءة ، مثل Up و Down و Ctrl + r وغيرها.


نعم / لا / إلغاء

وظيفة

#!/usr/bin/env bash
@confirm() {
  local message="$*"
  local result=''

  echo -n "> $message (Yes/No/Cancel) " >&2

  while [ -z "$result" ] ; do
    read -s -n 1 choice
    case "$choice" in
      y|Y ) result='Y' ;;
      n|N ) result='N' ;;
      c|C ) result='C' ;;
    esac
  done

  echo $result
}

استعمال

case $(@confirm 'Confirm?') in
  Y ) echo "Yes" ;;
  N ) echo "No" ;;
  C ) echo "Cancel" ;;
esac

تأكد مع إدخال المستخدم النظيف

وظيفة

#!/usr/bin/env bash
@confirm() {
  local message="$*"
  local result=3

  echo -n "> $message (y/n) " >&2

  while [[ $result -gt 1 ]] ; do
    read -s -n 1 choice
    case "$choice" in
      y|Y ) result=0 ;;
      n|N ) result=1 ;;
    esac
  done

  return $result
}

استعمال

if @confirm 'Confirm?' ; then
  echo "Yes"
else
  echo "No"
fi

آسف للنشر على هذا المنصب القديم. قبل عدة أسابيع كنت أواجه مشكلة مماثلة ، في حالتي كنت بحاجة إلى حل يعمل أيضًا ضمن برنامج التثبيت عبر الإنترنت ، على سبيل المثال: curl -Ss https://raw.github.com/_____/installer.sh | bash curl -Ss https://raw.github.com/_____/installer.sh | bash

استخدام read yesno < /dev/tty يعمل بشكل جيد بالنسبة لي:

echo -n "These files will be uploaded. Is this ok? (y/n) "
read yesno < /dev/tty

if [ "x$yesno" = "xy" ];then

   # Yes
else

   # No
fi

أمل أن هذا يساعد شخصاما.


أسهل طريقة لتحقيق ذلك بأقل عدد من الخطوط هي كما يلي:

read -p "<Your Friendly Message here> : y/n/cancel" CONDITION;

if [ "$CONDITION" == "y" ]; then
   # do something here!
fi

if مجرد مثال على ذلك: الأمر متروك لكيفية التعامل مع هذا المتغير.


أكثر عام سيكون:

function menu(){
    title="Question time"
    prompt="Select:"
    options=("Yes" "No" "Maybe")
    echo "$title"
    PS3="$prompt"
    select opt in "${options[@]}" "Quit/Cancel"; do
        case "$REPLY" in
            1 ) echo "You picked $opt which is option $REPLY";;
            2 ) echo "You picked $opt which is option $REPLY";;
            3 ) echo "You picked $opt which is option $REPLY";;
            $(( ${#options[@]}+1 )) ) clear; echo "Goodbye!"; exit;;
            *) echo "Invalid option. Try another one.";continue;;
         esac
     done
     return
}

اخترت Bash لهذا الغرض.

select result in Yes No Cancel
do
    echo $result
done

طريقة واحدة بسيطة للقيام بذلك مع xargs -p أو gnu parallel --interactive .

أنا أحب سلوك xargs أفضل قليلا لهذا لأنه ينفذ كل أمر على الفور بعد موجه مثل أوامر unix التفاعلية الأخرى ، بدلا من جمع yesses لتشغيل في النهاية. (يمكنك الضغط على Ctrl-C بعد الحصول على ما تريد.)

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

echo *.xml | xargs -p -n 1 -J {} mv {} backup/

كصديق لأمر سطر واحد استخدمت ما يلي:

while [ -z $prompt ]; do read -p "Continue (y/n)?" choice;case "$choice" in y|Y ) prompt=true; break;; n|N ) exit 0;; esac; done; prompt=;

صيغة طويلة مكتوبة ، تعمل على النحو التالي:

while [ -z $prompt ];
  do read -p "Continue (y/n)?" choice;
  case "$choice" in
    y|Y ) prompt=true; break;;
    n|N ) exit 0;;
  esac;
done;
prompt=;

لقد استعملت بيان case عدة مرات في مثل هذا السيناريو ، وذلك باستخدام حالة الدعوى هو وسيلة جيدة للذهاب حول هذا الموضوع. وهناك حلقة في while ، والتي تقوم بتكميم كتلة case ، التي تستخدم شرطًا منطقيًا يمكن تنفيذه من أجل الاحتفاظ بمزيد من التحكم في البرنامج ، وتحقيق العديد من المتطلبات الأخرى. بعد استيفاء جميع الشروط ، يمكن استخدام break التي تمرر التحكم إلى الجزء الرئيسي من البرنامج. أيضا ، لتلبية شروط أخرى ، بالطبع يمكن إضافة بيانات شرطية لمرافقة بنيات التحكم: بيان case و ممكن في while حلقة.

مثال على استخدام بيان case للوفاء بطلبك

#! /bin/sh 

# For potential users of BSD, or other systems who do not
# have a bash binary located in /bin the script will be directed to
# a bourne-shell, e.g. /bin/sh

# NOTE: It would seem best for handling user entry errors or
# exceptions, to put the decision required by the input 
# of the prompt in a case statement (case control structure), 

echo Would you like us to perform the option: "(Y|N)"

read inPut

case $inPut in
    # echoing a command encapsulated by 
    # backticks (``) executes the command
    "Y") echo `Do something crazy`
    ;;
    # depending on the scenario, execute the other option
    # or leave as default
    "N") echo `execute another option`
    ;;
esac

exit

للحصول على مربع إدخال لطيف يشبه ncurses استخدم مربع حوار الأوامر مثل هذا:

#!/bin/bash
if (dialog --title "Message" --yesno "Want to do something risky?" 6 25)
# message box will have the size 25x6 characters
then 
    echo "Let's do something risky"
    # do something risky
else 
    echo "Let's stay boring"
fi

يتم تثبيت حزمة الحوار بشكل افتراضي على الأقل مع SUSE Linux.


نسخة متعددة الخيارات:

ask () {                        # $1=question $2=options
    # set REPLY
    # options: x=..|y=..
    while $(true); do
        printf '%s [%s] ' "$1" "$2"
        stty cbreak
        REPLY=$(dd if=/dev/tty bs=1 count=1 2> /dev/null)
        stty -cbreak
        test "$REPLY" != "$(printf '\n')" && printf '\n'
        (
            IFS='|'
            for o in $2; do
                if [ "$REPLY" = "${o%%=*}" ]; then
                    printf '\n'
                    break
                fi
            done
        ) | grep ^ > /dev/null && return
    done
}

مثال:

$ ask 'continue?' 'y=yes|n=no|m=maybe'
continue? [y=yes|n=no|m=maybe] g
continue? [y=yes|n=no|m=maybe] k
continue? [y=yes|n=no|m=maybe] y
$

سيقوم بتعيين REPLY إلى y (داخل البرنامج النصي).


هذا شيء أضعه معًا:

#!/bin/sh

promptyn () {
    while true; do
        read -p "$1 " yn
        case $yn in
            [Yy]* ) return 0;;
            [Nn]* ) return 1;;
            * ) echo "Please answer yes or no.";;
        esac
    done
}

if promptyn "is the sky blue?"; then
    echo "yes"
else
    echo "no"
fi

أنا مبتدئ ، لذا خذ هذا بحبة ملح ، لكن يبدو أنه يعمل.


يمكنك استخدام الأمر المضمن read . استخدم الخيار -p المستخدم بسؤال.

منذ BASH4 ، يمكنك الآن استخدام -i لاقتراح إجابة ، بحيث لا يضطر المستخدم إلا للضغط على return لإدخالها:

read -e -p "Enter the path to the file: " -i "/usr/local/etc/" FILEPATH
echo $FILEPATH

(لكن تذكر استخدام خيار "readline" -e للسماح بتحرير الخطوط باستخدام مفاتيح الأسهم)


إن الطريقة الأبسط والأكثر اتاحة على نطاق واسع للحصول على ادخال المستخدم في مطالبة shell هي أمر read . أفضل طريقة لتوضيح استخدامه هي مظاهرة بسيطة:

while true; do
    read -p "Do you wish to install this program?" yn
    case $yn in
        [Yy]* ) make install; break;;
        [Nn]* ) exit;;
        * ) echo "Please answer yes or no.";;
    esac
done

طريقة أخرى ، أوضحها ستيفن هوغ ، هي أمر باش المختار. في ما يلي نفس المثال باستخدام select :

echo "Do you wish to install this program?"
select yn in "Yes" "No"; do
    case $yn in
        Yes ) make install; break;;
        No ) exit;;
    esac
done

مع select لا تحتاج إلى تطهير الإدخال - يعرض الخيارات المتاحة ، وتكتب رقمًا مطابقًا لاختيارك. كما أنه يعمل تلقائيًا ، لذلك لا داعي لحدوث حلقة while true لإعادة المحاولة إذا كانت تعطي مدخلات غير صالحة.

أيضا ، يرجى التحقق من الإجابة الممتازة التي كتبها F. Hauri.


inquire ()  {
  echo  -n "$1 [y/n]? "
  read answer
  finish="-1"
  while [ "$finish" = '-1' ]
  do
    finish="1"
    if [ "$answer" = '' ];
    then
      answer=""
    else
      case $answer in
        y | Y | yes | YES ) answer="y";;
        n | N | no | NO ) answer="n";;
        *) finish="-1";
           echo -n 'Invalid response -- please reenter:';
           read answer;;
       esac
    fi
  done
}

... other stuff

inquire "Install now?"

...

read -e -p "Enter your choice: " choice

يمكّن الخيار -e المستخدم من تحرير الإدخال باستخدام مفاتيح الأسهم.

إذا كنت ترغب في استخدام اقتراح كمدخل:

read -e -i "yes" -p "Enter your choice: " choice

الخيار -i يطبع إدخال موحية.


yn() {
  if [[ 'y' == `read -s -n 1 -p "[y/n]: " Y; echo $Y` ]];
  then eval $1;
  else eval $2;
  fi }
yn 'echo yes' 'echo no'
yn 'echo absent no function works too!'




scripting