Це тест або [або [[більш портативний як між оболонками, так і між іншими оболонками?


44

Я бачу, що можу зробити

$ [ -w /home/durrantm ] && echo "writable"
writable

або

$ test -w /home/durrantm && echo "writable"
writable

або

$ [[ -w /home/durrantm ]] && echo "writable"
writable

Мені подобається використовувати третій синтаксис. Чи вони рівнозначні в усіх напрямках і для всіх негативних та кращих випадків? Чи є якісь відмінності в портативності, наприклад, між bash на Ubuntu та на OS X або старішими / новішими версіями bash, наприклад, до / після 4.0 і чи обидва вони розширюють вирази однаково?


2
Що стосується [ … ]vs [[ … ]]vs test …, то в цьому здебільшого повторюваному запитанні є більш повні відповіді .
Жил "ТАК - перестань бути злим"

Для конкретного питання тестування на файл на завантажимість див. Також Як неінвазивно перевірити доступ для запису до файлу?
G-Man каже: "Відновіть Моніку"

1
Є приказка: "портативного коду немає, лише код, який перенесли". Моя порада щодо цього: Використовуйте найбільш читану форму (ймовірно, [[...]]) та спробуйте її на всіх платформах, які ви хочете підтримати. Немало користі для затемнення ваших сценаріїв, тому вони працюють на старовинних платформах, якими не користуєтесь ні ви, ні ваша цільова аудиторія. Це просто зробить ваш код важким для читання, введе непотрібні помилки і, можливо, навіть проблеми з безпекою (як це було для openssl).
stefan.schwetschke

Відповіді:


31

[є синонімом testкоманди, і це одночасно bash вбудована та окрема команда. Але [[це ключове слово bash і працює лише в деяких версіях. Тому з міркувань переносимості вам краще використовувати одинарний []абоtest

[ -w "/home/durrantm" ] && echo "writable"

2
[це вбудований POSIX або вбудований Bash?
Сандбург

@Sandburg POSIX - стандарт, тому він не може мати вбудовані модулі. Він не визначає, чи слід щось вбудовувати в інтерпретатора чи ні, саме те, що він повинен робити. Ці правила стосуються обох форм, так testі [однакових. Якщо оболонка може робити оцінку самостійно, це економить процес і робить його дещо швидшим, але результат повинен бути однаковим.
Бахсау

@Bachsau, який досі не відповідає на питання Сандбурга щодо того, чи поведінка [визначена POSIX і тому максимально портативна (що, здається, вся суть цього питання, якщо я не помиляюся)
JamesTheAwesomeDude

@Sandburg (і будь-хто інший, що натрапляє на це): Так, це схоже на обидва [та test є POSIX . Здається, немає ні "рекомендованого", хоча єдина різниця (?), Яку я можу помітити між ними, полягає в тому, що test"не визнає аргумент" - "[як роздільник, що вказує на кінець варіантів]" (? - маючи на увазі, що " - » буде визнаний закінченим аргументів в користь [, яку я не можу на насправді придумати хороший тестовий приклад для так чи інакше але я відволікся використання якого буде IMO, ... [виглядає елегантно, але testisclearly команда)
JamesTheAwesomeDude

35

Так, є відмінності. Найбільш портативними є testабо [ ]. Обидва вони є частиною специфікації POSIXtest .

if ... fiКонструкція також визначається POSIX і повинен бути повністю портативним.

[[ ]]Це kshфункція , яка також присутня в деяких версіях bash( всі сучасні ), в zshі , можливо , в інших , але не присутній в shабо dashабо різних інших простих оболонках.

Таким чином, щоб зробити ваші скрипти портативними, використовувати [ ], testабо if ... fi.


10
Зауважимо, bashі zshпідтримували [[дуже довгий час ( bashдодали його наприкінці 90-х, zshне пізніше 2000 року, і я би здивувався, якщо у нього коли-небудь бракує підтримки), тому ви навряд чи зможете зустріти версію або без [[. Зустріч з різною оболонкою, сумісною з POSIX (наприклад, dash) набагато ймовірніша.
чепнер

1
Цікавою особливістю [[є те, що розширення параметрів не потрібно цитувати: [[-f $file]]подія працює, якщо $fileмістить символи пробілу.
helpermethod

2
@helpermethod також вбудував регулярні вирази[[ $a =~ ^reg.*exp.*$' ]]
GnP

20

Зверніть увагу, [] && cmdце не те саме, що if .. fiбудівництво.

Іноді його поведінка досить схожа і [] && cmdзамість цього можна використовувати if .. fi. Але лише іноді. Якщо у вас є більше, ніж одна команда для виконання, якщо умова, або вам потрібно if .. else .. fiбути обережним і яка логіка.

Кілька прикладів:

[ -z "$VAR" ] && ls file || echo wiiii

Не те саме, що

if [ -z $VAR ] ; then
  ls file
else
  echo wiii
fi

тому що якщо lsне вдасться, echoбуде виконано те, чого не відбудеться if.

Інший приклад:

[ -z "$VAR" ] && ls file && echo wiii

не те саме, що

if [ -z "$VAR" ] ; then
   ls file
   echo $wiii
fi

хоча ця конструкція діятиме так само

[ -z "$VAR" ] && { ls file ; echo wiii ; }

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

Таким чином, відновлення твердження вище ми можемо сказати

[] && cmd == якщо перша команда успішна, виконайте наступну

if .. fi == якщо умова (яка може бути і тестовою командою), тоді виконайте команди (и)

Тож для портативності між [та [[використання [лише.

ifсумісний з POSIX. Тож якщо вам доведеться вибирати між собою [і ifвибирати, дивлячись на своє завдання та очікувану поведінку.


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

1
@evilsoup, оновлено. Можливо, я не найкращий пояснювач, хоча я сподіваюся, що це буде зрозуміло зараз.
пік

1
Дуже хороші бали, поспішайте. Я вважаю , безпечна форма вашого 1 - го прикладу буде: [ -z "$VAR" ] && { ls file; true; } || echo wiiii. Це трохи більш багатослівно, але все ж коротше, ніж if...fiконструкція.
PM 2Ring

Поставив питання про [vs [[vs test, а не про те, якщо ... fi vs &&, щоб це було ОДНЕ запитання. На жаль, це робить цю відповідь непозитною. Вибачення за те, що ми не сфокусувались на моєму питанні, спочатку привели до цього. Живіть і (намагайтеся) вчитися. :)
Майкл Дюрант

Я заявляв, що а) це в основному хороша відповідь, але це відповідь на інше питання. б) ви представляєте [і ifяк підзаголовок, але вони не є. Це насправді саме &&те, що замінює if. [виконує якийсь код і повертає статус, як би lsі grepхотілося. ifвиконання гілок залежно від стану повернення команди (оператора), заданої після if, це може бути будь-яка команда (оператор). &&виконує наступне твердження лише у тому випадку, якщо попереднє повернуло 0, подібно до простого if..then..fi.
GnP

9

Це на самому ділі , &&що заміна if, а НЕ test: ifзаяву в тестах сценаріїв оболонки повернулася команда в «успішний» (нуль) статус виходу; у вашому прикладі команда така [.

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

Команди тесту:

  • testє стандартизованою командою для оцінки властивостей рядків і файлів; у вашому прикладі ви виконуєте командуtest -w /home/durrantm
  • [- псевдонім цієї команди, однаково стандартизований, який має обов'язковий останній аргумент для ]того, щоб виглядати як виразний текст; не обманюйте, це все-таки лише команда (ви навіть можете виявити, що у вашій системі є файл, який називається /bin/[)
  • [[є розширеною версією тестової команди, вбудованої в деякі оболонки, але не є частиною того ж стандарту POSIX; вона включає додаткові параметри, які ви тут не використовуєте

Умовні вирази:

  • &&Оператор ( стандартизований тут ) виконує логічну операцію, шляхом оцінки двох команд і повернення 0 (який являє собою істинно) , якщо вони обидва повертають 0; вона оцінить другу команду лише у тому випадку, якщо перша поверне нуль, тому вона може бути використана як простий умовний
  • if ... then ... fiКонструкція ( стандартизовано тут ) використовує той же метод судити «правду», але дозволяє отримати список з'єднань операторів в thenреченні, а не однієї команди , яка забезпечується з допомогою &&короткого замикання, а також забезпечує elifі elseположення, які важко писати використовуючи лише &&і ||. Зауважте, що у ifвикладі немає дужок навколо умови .

Отже, наступні - всі однаково портативні та цілком еквівалентні переклади вашого прикладу:

  • test -w /home/durrantm && echo "writable"
  • [ -w /home/durrantm ] && echo "writable"
  • if test -w /home/durrantm; then echo "writable"; fi
  • if [ -w /home/durrantm ]; then echo "writable"; fi

Хоча наступне також є рівнозначним, але менш портативним через нестандартний характер [[:

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi

Так, я видалив if .... fi частину, щоб зробити це 1 питання.
Майкл Дюрант

7

Для портативності використовуйте test/ [. Але якщо вам не потрібна портативність, заради здоровості себе та інших, хто читає ваш сценарій [[. :)

Також дивіться What is the difference between test, [ and [[ ?у BashFAQ .


7

Якщо ви хочете портативність за межами світу, що нагадує Борну, тоді:

test -w /home/durrantm && echo writable

є найбільш портативним. Працює в оболонках Борна cshта rcсімей.

test -w /home/durrantm && echo "writable"

результат буде "writable"замість writableв оболонках rcсім'ї ( rc, es, akanga, де "не спеціальну).

[ -w /home/durrantm ] && echo writable

не працюватиме в оболонках cshабо rcсімей на системах, які не мають [команди $PATH(деякі, як відомо, мають, testале не його [псевдоніми).

if [ -w /home/durrantm ]; then echo writabe; fi

працює лише в оболонках родини Борн.

[[ -w /home/durrantm ]] && echo writable

працює лише в ksh(де вона виникла) zshта bash(всі 3 в родині Борна).

Жоден не працює в fishоболонці, де вам потрібно:

[ -w /home/durrantm ]; and echo writable

або:

if [ -w /home/durrantm ]; echo writable; end

0

Моя найважливіша причина вибору if foo; then bar; fiабо foo && barтого, чи важливий статус виходу всієї команди.

порівняти:

#!/bin/sh
set -e
foo && bar
do_baz

з:

#!/bin/sh
set -e
if foo; then bar; fi
do_baz

Ви можете подумати, що вони роблять те саме; однак якщо fooне вдасться (або помилково, залежно від вашої точки зору), то в першому прикладі do_baz не буде виконаний, оскільки сценарій вже буде завершений ... set -eІнструкція оболонки негайно вийти, якщо будь-яка команда поверне помилковий статус. Дуже корисно, якщо ви робите такі речі:

cd /some/directory
rm -rf *

Ви не хочете, щоб сценарій продовжував працювати, якщо cdне вдалося з будь-якої причини.


1
Ні в якому разі не fooвдасться не припинити сценарій в будь-якому випадку. Це особливий випадок для set -e(коли команда оцінюється як умова (ліворуч від деяких && / || або в умовах if / while / до / elsif ...). Невдача barвийде з оболонки в обох випадках.
Стефан Хазелас

2
Ви хочете cd /some/directory && rm -rf -- *або cd /some/directory || exit; rm -rf -- *(все ще не видаляє приховані файли). Мені особисто не подобається ідея використовувати set -eяк привід не робити зусиль, щоб написати правильний код.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.