Питання Як перевірити, чи містить рядок підрядка в Bash


У мене є рядок в Bash:

string="My string"

Як перевірити, чи містить він інший рядок?

if [ $string ?? 'foo' ]; then
  echo "It's there!"
fi

Де ?? є моїм невідомим оператором. Я використовую echo і grep?

if echo "$string" | grep 'foo'; then
  echo "It's there!"
fi

Це виглядає дещо незграбним.


1749
2017-10-23 12:37


походження


Привіт, якщо порожні рядки є помилковими, чому ви вважаєте це незграбним? Це був єдиний спосіб, який працював для мене, незважаючи на запропоновані рішення. - ericson.cepeda
Ви можете скористатись expr команда тут - cli__
Ось один для posix раковин: stackoverflow.com/questions/2829613/... - sehe


Відповіді:


Ви можете використовувати Відповідь Марка (* підстановки) за межами заяви на випадок, якщо ви використовуєте подвійні дужки:

string='My long string'
if [[ $string = *"My long"* ]]; then
  echo "It's there!"
fi

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


2504
2017-10-23 12:55



Також зауважте, що ви можете скасувати порівняння, просто перемикаючись на! = В тест. Дякую за відповідь! - Quinn Taylor
@Jonik: Ви можете пропустити шабанг або мати його як #!/bin/sh. Спробуй #!/bin/bash замість цього. - Dennis Williamson
Залиште пробіл між дужками та вмістом. - Paul Price
Вам не потрібно вказувати змінні всередині [[]]. Це буде працювати так само: [[$ string == $ голка ]] && echo знайдено - Orwellophile
@ Орвелофіль Обережно! Ви можете опустити подвійні лапки лише в першому аргументі [[. Побачити ideone.com/ZSy1gZ - nyuszika7h


Якщо ви вважаєте за краще підходити до регулярного виразу:

string='My string';

if [[ $string =~ .*My.* ]]
then
   echo "It's there!"
fi

388
2017-10-23 20:10



Довелося замінити регулярний вираз egrep в скрипті bash, це чудово працювало! - blast_hardcheese
Якщо вам потрібен пробіл, уникайте його за допомогою зворотної смуги риси: [[ $string =~ My\ s ]] - Matt Tardiff
The =~ оператор вже шукає весь рядок для матчу; в .*ось сторонні сторони. Крім того, котирування, як правило, краще, ніж зворотні риски: [[ $string =~ "My s" ]] - bukzor
@bukzor Quotes перестали працювати тут з Bash 3.2+: tiswww.case.edu/php/chet/bash/FAQ  E14). Це, мабуть, краще присвоїти змінній (використовуючи лапки), а потім порівняти. Подобається це: re="My s"; if [[ $string =~ $re ]] - seanf
Перевірте, чи це так НЕ містити рядок: if [[ ! "abc" =~ "d" ]] правда. - KrisWebDev


Я не впевнений у використанні statements if, але ви можете отримати подібний ефект із темою case:

case "$string" in 
  *foo*)
    # Do stuff
    ;;
esac

240
2017-10-23 12:47



Це, мабуть, найкраще рішення, оскільки він є портативним для posix оболонок. (a.k.a. немає bashisms) - technosaurus
@technosaurus Я вважаю це досить дивно, щоб критикувати "басизм" у питанні, що має тільки теги басла :) - P.P.
@ П.П. Це не стільки критика, скільки перевага більш універсального рішення над більш обмеженим. Будь ласка, враховуйте, що через кілька років люди (як і я) перестануть шукати цю відповідь, і, можливо, будемо раді знайти те, що корисно в ширшому обсязі, ніж оригінальний питання. Як кажуть у світі з відкритим вихідним кодом: "вибір хороший!" - Carl Smotricz
@technosaurus, FWIW [[ $string == *foo* ]] також працює в деяких версіях, сумісних із POSIX (наприклад, /usr/xpg4/bin/shна Solaris 10) і ksh (> = 88) - maxschlepzig
@ maxxschlepzig ... не, якщо є символи в $ IFS, такі як пробіл, новий рядки, табуляція або визначений користувачем роздільник ... справа працює, хоча без вибагливих змішаних цитат або необхідності натискати і поп-IFS - technosaurus


Сумісний відповідь

Оскільки вже існує багато відповідей, що використовують Bash-специфічні функції, існує спосіб працювати в менш небезпечних оболонках, як :

[ -z "${string##*$reqsubstr*}" ]

На практиці це могло б дати:

string='echo "My string"'
for reqsubstr in 'o "M' 'alt' 'str';do
  if [ -z "${string##*$reqsubstr*}" ] ;then
      echo "String '$string' contain substring: '$reqsubstr'."
    else
      echo "String '$string' don't contain substring: '$reqsubstr'."
    fi
  done

Це було перевірено під , ,  і  (busybox), і результат завжди:

String 'echo "My string"' contain substring: 'o "M'.
String 'echo "My string"' don't contain substring: 'alt'.
String 'echo "My string"' contain substring: 'str'.

В одну функцію

Як запитав @ EeroAaltonen, це версія одного і того ж демо, протестованого під однаковими оболонками:

myfunc() {
    reqsubstr="$1"
    shift
    string="$@"
    if [ -z "${string##*$reqsubstr*}" ] ;then
        echo "String '$string' contain substring: '$reqsubstr'.";
      else
        echo "String '$string' don't contain substring: '$reqsubstr'." 
    fi
}

Потім:

$ myfunc 'o "M' 'echo "My String"'
String 'echo "My String"' contain substring 'o "M'.

$ myfunc 'alt' 'echo "My String"'
String 'echo "My String"' don't contain substring 'alt'.

Примітка: ви повинні втекти або двічі додавати цитати та / або подвійні лапки:

$ myfunc 'o "M' echo "My String"
String 'echo My String' don't contain substring: 'o "M'.

$ myfunc 'o "M' echo \"My String\"
String 'echo "My String"' contain substring: 'o "M'.

Проста функція

Це було перевірено під ,  і звичайно :

stringContain() { [ -z "${2##*$1*}" ]; }

Це все, шановні!

Тоді зараз:

$ if stringContain 'o "M3' 'echo "My String"';then echo yes;else echo no;fi
no
$ if stringContain 'o "M' 'echo "My String"';then echo yes;else echo no;fi
yes

... Або якщо представлена ​​рядок може бути порожньою, як вказано @ Sjlver, функція стане:

stringContain() { [ -z "${2##*$1*}" ] && [ -z "$1" -o -n "$2" ]; }

або як запропоновано Коментар Адріана Гюнтера, уникаючи -o switchche:

stringContain() { [ -z "${2##*$1*}" ] && { [ -z "$1" ] || [ -n "$2" ] ;} ; }

З порожніми рядками:

$ if stringContain '' ''; then echo yes; else echo no; fi
yes
$ if stringContain 'o "M' ''; then echo yes; else echo no; fi
no

137
2017-12-08 23:06



Це було б ще краще, якщо ви зможете зрозуміти якийсь спосіб поставити це в функцію. - Eero Aaltonen
@ EeroAaltonen Як ви можете знайти мою (нову додану) функцію? - F. Hauri
Я знаю! знайти -name "*" | xargs grep "myfunc" 2> / dev / null - eggmatters
Це чудово, тому що це так сумісно. Одна помилка, однак: вона не працює, якщо рядок сіна стоїть порожній. Правильна версія буде string_contains() { [ -z "${2##*$1*}" ] && [ -n "$2" -o -z "$1" ]; }  Остання думка: чи містить порожня рядок порожній рядок? Версія над речами так (через -o -z "$1" частина) - Sjlver
+1 Дуже добре! Для мене я змінив рядок рядкаContain () {[-z "$ {1 ## * $ 2 *}"] && [-z "$ 2" -o -n "$ 1"]; }; "Пошук де" "Знайти що". Робота в busybox. Прийнята відповідь вище не працює в busybox. - user3439968


Слід пам'ятати, що сценарії командного середовища - це менша кількість мов і більше збірок команд. Інстинктивно ви думаєте, що ця "мова" вимагає від вас дотримуватися if з [ або a [[. Обидва ці просто команди, які повертають статус завершення, який вказує на успіх або невдачу (точно так само, як кожна інша команда). З цієї причини я б користуюсь grep, а не [ команда

Просто:

if grep -q foo <<<"$string"; then
    echo "It's there"
fi

Тепер, коли ви думаєте if як тестування статусу виходу команди, що слідує за нею (у комплекті з двокрапкою). Чому б не переглянути джерело рядка, який ви тестуєте?

## Instead of this
filetype="$(file -b "$1")"
if grep -q "tar archive" <<<"$filetype"; then
#...

## Simply do this
if file -b "$1" | grep -q "tar archive"; then
#...

The -q параметр робить grep не виводити нічого, оскільки ми тільки хочемо код повернення. <<< робить оболонку розгортання наступного слова та використовує його як вхід до команди, однорядкову версію << Тут документ (я не впевнений, чи це стандарт або басизм).


110
2017-10-27 14:57



вони називаються тут струни (3.6.7) Я вважаю, що це бастізм - alex.pilon
можна також використовувати Заміна процесу  if grep -q foo <(echo somefoothing); then - larsr
Вартість цього дуже дорога: роблячи grep -q foo <<<"$mystring" 1 вилка і є бастізм і echo $mystring | grep -q foo 2 вилки (одна для труби і друга для роботи /path/to/grep) - F. Hauri
Наші ідеї про "витрати" та "накладні витрати" перекошені на десятиріччя, в якому ми народилися. Я воював у "вбудованому тесті" проти "exec grep" воєн давно, але такі війни - це просто педантичні настільні ігри сьогодні. Ваші 4 мільярди кремнієвих транзисторів просто сидять там, просячи щось зробити (найчастіше витоку струму акумулятора). Довгий полюс в наметі - правильність довгого знімка. П.С. Я згоден з вашим коментарем :) - qneill
@qneill, Наші ідеї ефективності, що не мають значення, перекочуються апологетом постмодерністського оповіді. Лише на потужності марнотратних процесорів, які не потребують нічого подібного до паралелізму, ігнорування правди було просто незручно. Сьогоднішній сучасний офіс вимагає одночасності, що близько 99 буде називатися інтернет-шкалою. Загальні обчислення за розміром телефону є нормою, а не винятком. Тому, відзначаючи ефективність впровадження, не слід вимагати священних ентузіастів Ruby стіну. - TechZilla


Прийнята відповідь найкраще, але оскільки існує кілька способів зробити це, ось ще одне рішення:

if [ "$string" != "${string/foo/}" ]; then
    echo "It's there!"
fi

${var/search/replace} є $var з першим екземпляром search замінено на replace, якщо він знайшовся (він не змінюється $var) Якщо ви спробуєте замінити foo нічим, і рядок змінився, то очевидно foo був знайдений.


73
2017-10-23 14:35



вище, ніж:> `if [" $ string "! =" $ {string / foo /} "]; потім відлуння "Це там!" fi `корисний при використанні оболонки BusyBox зола. Прийняте рішення не працює з BusyBox, оскільки деякі регулярні вирази bash не реалізовані. - TPoschel
Це спотворений спосіб вирішення проблеми, але це хороша техніка для знання. - Sébastien
нерівність різниці. Дуже дивна думка! я це люблю - nitinr708


Таким чином, існує безліч корисних рішень для питання, але що найшвидше / використовує найменший ресурс?

Повторні тести з використанням цього кадру:

/usr/bin/time bash -c 'a=two;b=onetwothree; x=100000; while [ $x -gt 0 ]; do TEST ; x=$(($x-1)); done'

Замінюючи тест кожного разу:

[[ $b =~ $a ]]           2.92user 0.06system 0:02.99elapsed 99%CPU

[ "${b/$a//}" = "$b" ]   3.16user 0.07system 0:03.25elapsed 99%CPU

[[ $b == *$a* ]]         1.85user 0.04system 0:01.90elapsed 99%CPU

case $b in *$a):;;esac   1.80user 0.02system 0:01.83elapsed 99%CPU

doContain $a $b          4.27user 0.11system 0:04.41elapsed 99%CPU

(doContain був у відповіді Ф. Хори)

І для хихикань:

echo $b|grep -q $a       12.68user 30.86system 3:42.40elapsed 19%CPU !ouch!

Таким чином, простий варіант заміщення визначається як у розширеному тесті, так і у випадку. Корпус портативний.

Трубопровід до 100000 ГПП передбачувано болючий! Старе правило про використання зовнішніх утиліт без необхідності залишається вірним.


31
2017-08-27 19:42



Чудовий орієнтир. Переконаний, що я використовую [[ $b == *$a* ]]. - Mad Physicist
Якщо я читаю це правильно, case виграє з найменшим загальним споживанням часу. Ви не маєте зірочки після $b in *$a хоча Я отримую трохи швидше результати для [[ $b == *$a* ]] ніж для case з виправленою помилкою, але це, звичайно, може залежати і від інших факторів. - tripleee
ideone.com/5roEVtмій експеримент з деякими додатковими помилками виправлено та тести для іншого сценарію (де рядок насправді відсутній у довшому рядку). Результати в основному аналогічні; [[ $b == *$a* ]] швидко і швидко case майже так само швидко (і приємно сумісна з POSIX). - tripleee


Це також працює:

if printf -- '%s' "$haystack" | egrep -q -- "$needle"
then
  printf "Found needle in haystack"
fi

І тест негативний:

if ! printf -- '%s' "$haystack" | egrep -q -- "$needle"
then
  echo "Did not find needle in haystack"
fi

Я вважаю, що цей стиль трохи класичніший - менш залежний від особливостей оболонки Bash.

The -- аргумент - це чиста параноя POSIX, яка використовується для захисту від вхідних рядків, подібних до параметрів, таких як --abc або -a.

Примітка. У жорсткому циклі цей код буде багато чого повільніше, ніж використання внутрішніх функцій Bash shell, оскільки буде створено один (або два) окремі процеси і буде з'єднано через труби.


22
2017-12-01 15:41



OP чітко позначено тегом bash. - gniourf_gniourf
... але ОП не каже, яка версія з bash; Наприклад, більш старі bash (наприклад, соляри часто мають) не можуть містити ці нові функції bash. (Я зіткнувся з цією конкретною проблемою (баш-зразок збігу не реалізований) на солярі w / bash 2.0) - michael
echo Непостійна, ви повинні використовувати printf '%s' "$haystack замість цього. - nyuszika7h
Ні, просто уникай echo взагалі за щось, крім буквового тексту, без втечі, що не починається з a -. Це може працювати для вас, але це не переноситься. Навіть баш echo буде вести себе по-різному залежно від того, чи є xpg_echo опція встановлена. П.С .: Я забув закрити подвійну цитату в моєму попередньому коментарі. - nyuszika7h
@kevinarpe я не впевнений -- не вказано в Специфікація POSIX для printf, але ви повинні використовувати printf '%s' "$anything" у всякому разі, щоб уникнути проблем, якщо $anything містить а % характер - nyuszika7h


Як про це:

text="   <tag>bmnmn</tag>  "
if [[ "$text" =~ "<tag>" ]]; then
   echo "matched"
else
   echo "not matched"
fi

11
2018-02-09 06:15



= ~ це для відповідності регулярного виразу, отже, занадто потужний для цілі OP. - Georgi K
Найкраще і найпростіше вирішення, поки що. - ivanleoncz


Це переповнення стека відповісти був єдиним, хто захопив простір і стирання символів:

# For null cmd arguments checking   
to_check=' -t'
space_n_dash_chars=' -'
[[ $to_check == *"$space_n_dash_chars"* ]] && echo found

9
2017-08-26 10:13



Немає необхідності export в цьому ізольованому прикладі. Якщо змінна повинна бути видимою для команд, запущених сценарієм #rom tkis, то вам потрібно експортувати його. - tripleee
Вилучено непотрібне exportс - Teemu Leisti