Як можна перевірити, чи починається рядок з якогось значення в Bash?


744

Я хотів би перевірити, чи починається рядок з "node", наприклад "node001". Щось на зразок

if [ $HOST == user* ]
  then
  echo yes
fi

Як я можу це зробити правильно?


Далі мені потрібно поєднувати вирази, щоб перевірити, чи HOST або "user1", або починається з "node"

if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

> > > -bash: [: too many arguments

Як я можу це зробити правильно?


7
Не надто спокушайтесь поєднувати вирази. Можливо, виглядає погірше два окремі умови, хоча ви можете давати кращі повідомлення про помилки та полегшити налагодження сценарію. Крім того, я б не уникав особливостей басу. Перемикач - це шлях.
hendry

Відповіді:


1072

Цей фрагмент в Посібнику з розширеного сценарію Bash говорить:

# The == comparison operator behaves differently within a double-brackets
# test than within single brackets.

[[ $a == z* ]]   # True if $a starts with a "z" (wildcard matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).

Тож у вас це було майже правильно; вам потрібні були подвійні дужки, а не окремі дужки.


Що стосується вашого другого питання, ви можете написати це так:

HOST=user1
if  [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes1
fi

HOST=node001
if [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes2
fi

Яке відлуння

yes1
yes2

ifСинтаксис Баша важко звикнути (ІМО).


12
Під регексом ви маєте на увазі [[$ a = ~ ^ z. *]]?
JStrahl

3
Так є функціональна різниця між [[ $a == z* ]]і [[ $a == "z*" ]]? Іншими словами: чи працюють вони по-різному? А що конкретно ви маєте на увазі, коли ви говорите "$ a дорівнює z *"?
Нільс Бом,

5
Вам не потрібен роздільник операторів ";" якщо ви поставите "тоді" на свою лінію
Yaza

6
Просто для повноти: Щоб перевірити, чи рядок ENDS з ...:[[ $a == *com ]]
lepe

4
ABS - це невдалий вибір посилань - це дуже багато W3Schools bash, повний застарілого контенту та прикладів поганої практики; канал freenode #bash намагається відмовити його від використання щонайменше з 2008 року . Будь-який шанс перейменуватися на BashFAQ №31 ? (Я б також запропонував вікі Баш-хакерів, але зараз це вже трохи).
Чарльз Даффі

207

Якщо ви використовуєте останню версію Bash (v3 +), я пропоную оператор порівняння регулярних виразів Bash =~, наприклад,

if [[ "$HOST" =~ ^user.* ]]; then
    echo "yes"
fi

Для відповідності this or thatу регулярному вираженні використовуйте |, наприклад,

if [[ "$HOST" =~ ^user.*|^host1 ]]; then
    echo "yes"
fi

Примітка - це "правильний" синтаксис регулярного вираження.

  • user*означає useі нульове або більше випадків виникнення r, так useі userrrrбуде відповідати.
  • user.*засоби userі нульові або-більше входжень будь-якого символу, тому user1, userXбудуть відповідати.
  • ^user.*означає відповідати шаблону user.*на початку $ HOST.

Якщо ви не знайомі з синтаксисом регулярних виразів, спробуйте звернутися до цього ресурсу .


Спасибі, Брабстер! До оригінального допису я додав нове запитання про те, як поєднувати вирази у випадку, якщо це заслінка.
Тім

2
Шкода, що прийнята відповідь нічого не говорить про синтаксис регулярного вираження.
CarlosRos

20
=~Оператор FYI Bash здійснює відповідність регулярних виразів лише у тому випадку, коли права частина НЕ БЕЗПЕЧЕНА. Якщо ви цитуєте праву частину "Будь-яка частина шаблону може бути процитована, щоб змусити його відповідати як рядок". (1.) не забудьте завжди ставити регулярні вирази праворуч, не цитуючи, і (2.) якщо ви зберігаєте регулярний вираз у змінній, обов'язково НЕ цитуйте праву частину, коли ви робите розширення параметрів.
Тревор Бойд Сміт

145

Я завжди намагаюся дотримуватися POSIX shзамість того, щоб використовувати розширення Bash, оскільки одним із головних моментів сценаріїв є портативність (окрім підключення програм, а не їх заміни).

У sh, існує простий спосіб перевірити стан "є префіксом".

case $HOST in node*)
    # Your code here
esac

З огляду на те, скільки років має таємничий та нахабний шум (а Bash - це не ліки: це складніше, менш послідовно і менш портативно), я хотів би зазначити дуже приємний функціональний аспект: Хоча деякі елементи синтаксису, як-от caseвбудовані , отримані конструкції не відрізняються від будь-якої іншої роботи. Вони можуть бути складені так само:

if case $HOST in node*) true;; *) false;; esac; then
    # Your code here
fi

Або навіть коротше

if case $HOST in node*) ;; *) false;; esac; then
    # Your code here
fi

Або навіть коротше (просто представити !як мовний елемент - але це поганий стиль зараз)

if ! case $HOST in node*) false;; esac; then
    # Your code here
fi

Якщо вам подобається явний текст, створіть свій власний мовний елемент:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }

Хіба це насправді не дуже приємно?

if beginswith node "$HOST"; then
    # Your code here
fi

А оскільки shв основному це лише завдання та списки рядків (і внутрішні процеси, з яких складаються завдання), ми зараз можемо навіть зробити кілька легких функціональних програмувань:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }
checkresult() { if [ $? = 0 ]; then echo TRUE; else echo FALSE; fi; }

all() {
    test=$1; shift
    for i in "$@"; do
        $test "$i" || return
    done
}

all "beginswith x" x xy xyz ; checkresult  # Prints TRUE
all "beginswith x" x xy abc ; checkresult  # Prints FALSE

Це елегантно. Не те, що я б прихильник використання shдля чогось серйозного - це надто швидко розбивається на вимоги реального світу (немає лямбда, тому ми повинні використовувати рядки. Але виклик функції введення рядками не можливий, труби неможливі тощо)


12
+1 Це не тільки портативний портал, він також читабельний, ідіоматичний та елегантний (для сценарію оболонки). Він також природно поширюється на кілька моделей; case $HOST in user01 | node* ) ...
tripleee

Чи існує назва цього форматування коду? if case $HOST in node*) true;; *) false;; esac; then Я бачив це тут і там, на моє око це виглядає якось викручено.
Нільс Бом

@NielsBom Я не знаю, що саме ти маєш на увазі під форматуванням, але мій погляд був у тому, що код оболонки дуже зручний для компонування . caseКоманди Becaues - це команди, вони можуть зайти всередину if ... then.
Jo So

Я навіть не бачу, чому він є композитивним, я не розумію достатнього сценарію оболонки для цього :-) Моє запитання про те, як цей код використовує невідповідні дужки та подвійні крапки з комою. Він не схожий на сценарій оболонки, який я бачив раніше, але я, можливо, звик бачити сценарій bash більше, ніж скрипт sh, щоб це було.
Нільс Бом,

Примітка: Це повинно бути в beginswith() { case "$2" in "$1"*) true;; *) false;; esac; }іншому випадку , якщо $1має буквальне *або ?це може дати неправильну відповідь.
LLFourn

80

Ви можете вибрати лише ту частину рядка, яку ви хочете перевірити:

if [ "${HOST:0:4}" = user ]

Для подальшого запитання ви можете використовувати АБО :

if [[ "$HOST" == user1 || "$HOST" == node* ]]

8
Вам слід подвоїти котирування${HOST:0:4}
Jo So

@Jo Отже: в чому причина?
Пітер Мортенсен

@PeterMortensen, спробуйтеHOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
Jo So

60

Я віддаю перевагу іншим методам, які вже розміщені, але деякі люди люблять використовувати:

case "$HOST" in 
    user1|node*) 
            echo "yes";;
        *)
            echo "no";;
esac

Редагувати:

Я додав ваших заступників до заяви справи вище

У відредагованій версії у вас занадто багато дужок. Це повинно виглядати так:

if [[ $HOST == user1 || $HOST == node* ]];

Спасибі, Деннісе! До оригінального допису я додав нове запитання про те, як поєднувати вирази у випадку, якщо це заслінка.
Тім

11
"деяким людям подобається ...": ця версія більш портативна в різних версіях і оболонках.
carlosayam

За допомогою операторів ви можете залишити лапки навколо змінної, оскільки не відбувається розщеплення слів. Я знаю, що це безглуздо і непослідовно, але я люблю залишати там цитати, щоб зробити її місцевішою більш привабливою.
Jo So

І в моєму випадку я раніше повинен був залишити цитати): "/ *") не працював, / *) зробив. (Я шукаю рядки, що починаються з / тобто абсолютних шляхів)
JosiahYoder-deactive за винятком ..

36

Хоча я вважаю, що більшість відповідей тут є правильними, багато з них містять непотрібні башизми. Розширення параметрів POSIX дає вам усе необхідне:

[ "${host#user}" != "${host}" ]

і

[ "${host#node}" != "${host}" ]

${var#expr}смуги найменше узгодження префікса exprз ${var}і повертає. Отже , якщо ${host}це НЕ почати з user( node), ${host#user}( ${host#node}) є такою ж , як ${host}.

exprдозволяє виконувати fnmatch()символи, таким чином ${host#node??}і друзі також працюють.


2
Я заперечую, що башизм [[ $host == user* ]]може бути необхідним, оскільки він набагато читає, ніж [ "${host#user}" != "${host}" ]. Отже, ви керуєте середовищем, де виконується сценарій (орієнтований на останні версії bash), перший варіант є кращим.
x-yuri

2
@ x-yuri Щиро кажучи, я просто упакував би це у has_prefix()функцію і більше ніколи не дивлюся на це.
dhke

30

Оскільки #має значення Bash, я дійшов до наступного рішення.

Крім того, мені подобається краще упакувати рядки з "" для подолання пробілів тощо.

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi

Це саме те, що мені було потрібно. Дякую!
Ionică Bizău

дякую, мені також було цікаво, як зіставити рядок, починаючи з blah:, схоже, це відповідь!
Anentropic

12

Додавши трохи детальніше синтаксису до відповіді найвищого рангу Марка Рушакова.

Вираз

$HOST == node*

Можна також записати як

$HOST == "node"*

Ефект той самий. Просто переконайтеся, що підстановка знаходиться за межами цитованого тексту. Якщо підстановка знаходиться всередині лапок, вона буде інтерпретуватися буквально (тобто не як підстановка).


8

@OP, для обох питань ви можете використовувати case / esac:

string="node001"
case "$string" in
  node*) echo "found";;
  * ) echo "no node";;
esac

Друге питання

case "$HOST" in
 node*) echo "ok";;
 user) echo "ok";;
esac

case "$HOST" in
 node*|user) echo "ok";;
esac

Або Bash 4.0

case "$HOST" in
 user) ;&
 node*) echo "ok";;
esac

Зверніть увагу, що ;&доступний лише на Bash> = 4.
Призупинено до подальшого повідомлення.

6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

не працює, тому що все [, [[і testвизнати той же нерекурсівние граматику. Дивіться розділ УМОВНІ ВИРАЗИ на сторінці Вашого чоловіка.

Як осторонь, йдеться у SUSv3

Умовна командна команда KornShell (подвійна дужка [[]] ) була видалена з опису мови командної оболонки в ранній пропозиції. Були висловлені заперечення, що справжньою проблемою є неправильне використання тестової команди ( [ ), а введення її в оболонку - це неправильний спосіб виправити проблему. Натомість достатня належна документація та нове застережене слово оболонки ( ! ).

Тести , які вимагають кілька тестових операцій можуть бути зроблені на рівні оболонки з використанням індивідуальних викликів з тіста команд і оболонок логічних виразів , а не з допомогою схильних до помилок -o прапор випробування .

Вам потрібно буде написати це так, але тест це не підтримує:

if [ $HOST == user1 -o $HOST == node* ];
then
echo yes
fi

test використовує = для рівності рядків, і що ще важливіше, він не підтримує відповідність шаблону.

case/ esacмає гарну підтримку відповідності шаблонів:

case $HOST in
user1|node*) echo yes ;;
esac

Він має додаткову перевагу, що він не залежить від Bash, а синтаксис є портативним. З єдиної специфікації Unix , Мова командної оболонки :

case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac

1
[і testє вбудованими Bash, а також зовнішніми програмами. Спробуйте type -a [.
Призупинено до подальшого повідомлення.

Велике спасибі за пояснення проблем із "складовою чи", @ просто хтось - шукав саме щось подібне! Ура! PS примітка (не пов'язані з ФП): if [ -z $aa -or -z $bb ]; ... дає " bash: [: -or: очікується бінарний оператор "; проте if [ -z "$aa" -o -z "$bb" ] ; ...проходить.
sdaau

2

grep

Забувши продуктивність, це POSIX і виглядає приємніше, ніж caseрішення:

mystr="abcd"
if printf '%s' "$mystr" | grep -Eq '^ab'; then
  echo matches
fi

Пояснення:


2

Я змінив відповідь @ markrushakoff, щоб зробити це функцією дзвінка:

function yesNo {
  # Prompts user with $1, returns true if response starts with y or Y or is empty string
  read -e -p "
$1 [Y/n] " YN

  [[ "$YN" == y* || "$YN" == Y* || "$YN" == "" ]]
}

Використовуйте його так:

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] Y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] yes
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n]
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] ddddd
false

Ось більш складна версія, яка передбачає задане значення за замовчуванням:

function toLowerCase {
  echo "$1" | tr '[:upper:]' '[:lower:]'
}

function yesNo {
  # $1: user prompt
  # $2: default value (assumed to be Y if not specified)
  # Prompts user with $1, using default value of $2, returns true if response starts with y or Y or is empty string

  local DEFAULT=yes
  if [ "$2" ]; then local DEFAULT="$( toLowerCase "$2" )"; fi
  if [[ "$DEFAULT" == y* ]]; then
    local PROMPT="[Y/n]"
  else
    local PROMPT="[y/N]"
  fi
  read -e -p "
$1 $PROMPT " YN

  YN="$( toLowerCase "$YN" )"
  { [ "$YN" == "" ] && [[ "$PROMPT" = *Y* ]]; } || [[ "$YN" = y* ]]
}

Використовуйте його так:

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N]
false

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N] y
true

$ if yesNo "asfd" y; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false

-5

Ще одна річ, яку ви можете зробити, - catце те, що ви лунаєте та лунаєтеinline cut -c 1-1

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.