В Bash, как я могу проверить, начинается ли строка с некоторого значения?

747

Я хотел бы проверить, если строка начинается с "узла", например, "узел001". Что-то вроде

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
Не поддавайтесь соблазну сочетать выражения. Может показаться уродливым иметь два отдельных условия, хотя вы можете дать более качественные сообщения об ошибках и упростить отладку сценария. Также я бы избегал функций bash. Переключатель - это путь.
Хендри

Ответы:

1075

В этом фрагменте руководства Advanced Bash Scripting говорится:

# 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синтаксису Bash трудно привыкнуть (IMO).

Марк Рушаков
источник
12
Для регулярного выражения вы имеете в виду [[$ a = ~ ^ z. *]]?
JStrahl
3
Так есть ли функциональная разница между [[ $a == z* ]]и [[ $a == "z*" ]]? Другими словами: они работают по-другому? А что конкретно вы имеете в виду, когда говорите "$ a равен z *"?
Нильс Бом
5
Вам не нужен разделитель операторов ";" если вы поставите «тогда» на собственной линии
Yaza
6
Просто для полноты: Для того, чтобы проверить , если строка заканчивается ...:[[ $a == *com ]]
Лепе
4
АБС - неудачный выбор ссылок - это в значительной степени W3Schools, полный устаревшего контента и примеров плохой практики; канал freenode #bash пытается препятствовать его использованию по крайней мере с 2008 года . Есть ли шанс на переиздание в BASFAQ # 31 ? (Я бы также предложил вики Bash-Hackers, но сейчас она не работает).
Чарльз Даффи
207

Если вы используете последнюю версию Bash (v3 +), я предлагаю =~, например, оператор сравнения Bash regex ,

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.

Если вы не знакомы с синтаксисом регулярных выражений, попробуйте обратиться к этому ресурсу .

brabster
источник
Спасибо, Брабстер! Я добавил к оригинальному сообщению новый вопрос о том, как комбинировать выражения в if cluase.
Тим
2
Жаль, что принятый ответ ничего не говорит о синтаксисе регулярного выражения.
CarlosRos
20
К вашему сведению, =~оператор Bash выполняет сопоставление регулярных выражений только в том случае, если правая часть не задана. Если вы цитируете правую часть, «любая часть шаблона может быть заключена в кавычки, чтобы заставить его быть сопоставленным как строка». (1.) всегда ставьте регулярные выражения справа без кавычек и (2.) если вы храните свое регулярное выражение в переменной, НЕ ставьте кавычки в правой части при расширении параметра.
Тревор Бойд Смит
145

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

В sh, есть простой способ проверить состояние "is-prefix".

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

Учитывая, насколько стар, тайен и неуклюжий sh (а 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команды есть команды, они могут идти внутрь if ... then.
Джо Со
Я даже не понимаю, почему это компоновка, я не понимаю достаточно сценариев оболочки для этого :-) Мой вопрос о том, как этот код использует несоответствующие скобки и двойные точки с запятой. Это не похоже на сценарий оболочки, который я видел раньше, но я могу привыкнуть видеть сценарий 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 Итак: в чем причина?
Питер Мортенсен
@PeterMortensen, попробуйтеHOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
Джо Так
60

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

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

Редактировать:

Я добавил твои альтернативы к описанию дела выше

В вашей отредактированной версии у вас слишком много скобок. Это должно выглядеть так:

if [[ $HOST == user1 || $HOST == node* ]];
Приостановлено до дальнейшего уведомления.
источник
Спасибо, Деннис! Я добавил к оригинальному сообщению новый вопрос о том, как комбинировать выражения в if cluase.
Тим
11
«некоторым людям нравится ...»: этот более переносим между версиями и оболочками.
Карлосаям
С помощью операторов case вы можете оставить кавычки вокруг переменной, так как разделение слов не происходит. Я знаю, что это бессмысленно и непоследовательно, но я хотел бы оставить там цитаты, чтобы сделать их более привлекательными на местном уровне.
Джо Со
И в моем случае я должен был опустить цитаты раньше): "/ *") не сработало, / *) сработало. (Я ищу строки, начинающиеся с /, т.
Джосия Йодер
36

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

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

а также

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

${var#expr}полосы наименьшее согласование префикса exprиз ${var}и возвращает. Следовательно , если ${host}это не начать с user( node), ${host#user}( ${host#node}) является таким же , как ${host}.

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

dhke
источник
2
Я бы сказал, что [[ $host == user* ]]может быть необходим башизм , поскольку он гораздо более читабелен, чем [ "${host#user}" != "${host}" ]. Таким образом, при условии, что вы управляете средой, в которой выполняется скрипт (нацелены на последние версии bash), предпочтительнее первое.
x-yuri
2
@ x-yuri Честно говоря, я бы просто упаковал это в has_prefix()функцию и больше никогда не смотрел на нее.
18:30
30

Поскольку #имеет значение в Bash, я получил следующее решение.

Кроме того, мне нравится упаковывать строки с "", чтобы преодолеть пробелы и т. Д.

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi
Озма
источник
Это было именно то, что мне было нужно. Спасибо!
Ионика Бизэу
спасибо, мне также было интересно, как сопоставить строку, начинающуюся с blah:, похоже, это ответ!
Anentropic
12

Добавление чуть более подробной информации о синтаксисе к ответу самого высокого ранга Марка Рушакова.

Выражение

$HOST == node*

Может также быть написано как

$HOST == "node"*

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

MrPotatoHead
источник
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
ghostdog74
источник
Обратите внимание, что ;&доступно только в Bash> = 4.
Приостановлено до дальнейшего уведомления.
6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

не работает, потому что все [, [[и testпризнать тот же нерекурсивную грамматику. Смотрите раздел УСЛОВНЫЕ ВЫРАЖЕНИЯ на вашей странице руководства Bash.

Кроме того, SUSv3 говорит

Условная команда, полученная из KornShell (двойная скобка [[]] ), была удалена из описания языка команд оболочки в раннем предложении. Были высказаны возражения, что реальная проблема заключается в неправильном использовании тестовой команды ( [ ), а помещение ее в оболочку является неправильным способом решения проблемы). Вместо этого достаточно правильной документации и нового зарезервированного слова оболочки ( ! ).

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

Вам нужно написать это так, но test не поддерживает это:

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 примечание (не имеет отношения к OP) if [ -z $aa -or -z $bb ]:; ... дает " bash: [: -or: ожидается двоичный оператор "; однако if [ -z "$aa" -o -z "$bb" ] ; ...проходит.
Сдау
2

grep

Забывая о производительности, это POSIX и выглядит лучше, чем caseрешения:

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

Объяснение:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
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

Ричард Тан
источник