Какова цель встроенной GNU Bash (двоеточие)?

336

Какова цель команды, которая ничего не делает, будучи всего лишь лидером комментариев, но на самом деле является встроенной оболочкой?

Это медленнее, чем вставка комментария в ваши сценарии примерно на 40% за вызов, что, вероятно, сильно варьируется в зависимости от размера комментария. Единственные возможные причины, которые я вижу, это:

# poor man's delay function
for ((x=0;x<100000;++x)) ; do : ; done

# inserting comments into string of commands
command ; command ; : we need a comment in here for some reason ; command

# an alias for `true' (lazy programming)
while : ; do command ; done

Я предполагаю, что я действительно ищу то, какое историческое применение это могло иметь.

amphetamachine
источник
5
Тот же вопрос по Unix и Linux : для чего служит встроенное двоеточие?
Калеб
69
@Caleb - я спросил это за два года до этого.
амфетамина
Я бы не сказал, что команда, которая возвращает определенное значение, «ничего не делает». Если только функциональное программирование не состоит в том, чтобы «ничего не делать». :-)
LarsH

Ответы:

415

Исторически сложилось , что у снарядов Борна не было trueи falseвстроенных команд. trueвместо этого был просто псевдоним :, и falseчто-то вроде let 0.

:немного лучше, чем trueдля переносимости на древние раковины, полученные из Борна. В качестве простого примера рассмотрим отсутствие !оператора конвейера или оператора ||списка (как это было в случае некоторых древних оболочек Борна). Это оставляет elseпредложение ifоператора как единственное средство для ветвления на основе состояния выхода:

if command; then :; else ...; fi

Поскольку ifтребуется не пустое thenпредложение, а комментарии не считаются непустыми, оно :служит неактивным .

В настоящее время (то есть: в современном контексте) вы обычно можете использовать либо :или true. Оба указываются в POSIX, и некоторые из них trueлегче читать. Однако есть одна интересная разница: :это так называемый POSIX специальный встроенные , а trueявляется обычным встроенным .

  • Специальные встроенные элементы должны быть встроены в оболочку; Обычные встроенные функции только «типично» встроены, но это не гарантируется строго. Обычно не должно быть обычной программы, названной :с помощью функции truePATH в большинстве систем.

  • Вероятно, самое важное отличие состоит в том, что со специальными встроенными модулями любая переменная, установленная встроенной функцией - даже в среде во время простой оценки команды - сохраняется после завершения команды, как показано здесь с использованием ksh93:

    $ unset x; ( x=hi :; echo "$x" )
    hi
    $ ( x=hi true; echo "$x" )
    
    $

    Обратите внимание, что Zsh игнорирует это требование, как и GNU Bash, за исключением случаев, когда он работает в режиме совместимости с POSIX, но все другие основные оболочки, производные от POSIX sh, соблюдают это, включая dash, ksh93 и mksh.

  • Другое отличие состоит в том, что обычные встроенные модули должны быть совместимы с exec- продемонстрировано здесь с использованием Bash:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIX также явно отмечает, что это :может быть быстрее, чем true, хотя, конечно, это детали реализации.

граф
источник
Вы имели в виду, что обычные встроенные модули не должны быть совместимы с exec?
Старый Pro
1
@OldPro: Нет, он прав в том, что trueявляется обычным встроенным, но он неверен в том, что execиспользует /bin/trueвместо встроенного.
Приостановлено до дальнейшего уведомления.
1
@DennisWilliamson Я просто хотел сказать, что спецификация сформулирована. Подразумевается, что обычные встроенные программы также должны иметь отдельную версию.
ormaaj
17
+1 Отличный ответ. Я все еще хотел бы отметить использование для инициализации переменных, как : ${var?not initialized}и др.
tripleee
Более или менее несвязанное наблюдение. Вы сказали, :что это специальная встроенная функция, которая не должна иметь названную им функцию. Но разве это не самый распространенный пример разветвленной бомбы, :(){ :|: & };:называющей функцию именем :?
Чонг
63

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

#!/bin/bash
if [[ "$VERBOSE" == "" || "$VERBOSE" == "0" ]]; then
    vecho=":"     # no "verbose echo"
else
    vecho=echo    # enable "verbose echo"
fi

$vecho "Verbose echo is ON"

таким образом

$ ./vecho
$ VERBOSE=1 ./vecho
Verbose echo is ON

Это делает для чистого сценария. Это не может быть сделано с помощью «#».

Также,

: >afile

это один из самых простых способов гарантировать, что «afile» существует, но имеет длину 0.

Кевин Литтл
источник
20
>afileеще проще и достигает того же эффекта.
граф
2
Круто, я буду использовать этот трюк $ vecho для упрощения сценариев, которые я поддерживаю.
BarneySchmale
5
Какая польза от цитирования двоеточия vecho=":"? Просто для удобства чтения?
leoj
56

Полезное приложение для: - если вы заинтересованы только в использовании расширений параметров для их побочных эффектов, а не в том, чтобы фактически передавать их результат команде. В этом случае вы используете PE в качестве аргумента либо:, либо false, в зависимости от того, хотите ли вы выйти из состояния 0 или 1. Примером может быть : "${var:=$1}". Так :как это встроенный, он должен быть довольно быстрым.

ormaaj
источник
2
Вы также можете использовать его для побочных эффектов арифметического расширения: : $((a += 1))( ++и --операторы не должны быть реализованы в соответствии с POSIX.). В bash, ksh и других возможных оболочках вы можете сделать это: ((a += 1))или, ((a++))но это не указано в POSIX.
Пабук
@pabouk Да, это все правда, хотя (())указывается как дополнительная функция. «Если последовательность символов, начинающаяся с« ((», будет проанализирована оболочкой как арифметическое расширение, если ей предшествует символ« $ », оболочки, которые реализуют расширение, посредством которого« ((выражение)) »оценивается как арифметическое выражение, можно обработать "((" как введение в качестве арифметической оценки вместо группирующей команды. "
ormaaj
50

:также может быть для комментария блока (аналогично / * * / на языке Си). Например, если вы хотите пропустить блок кода в вашем скрипте, вы можете сделать это:

: << 'SKIP'

your code block here

SKIP
zagpoint
источник
3
Плохая идея. Любые подстановки команд внутри этого документа все еще обрабатываются.
Чепнер
33
Не такая плохая идея. Вы можете избежать изменения / замены переменных в этом документе, заключив разделитель в одинарные кавычки:: << 'SKIP'
Rondo
1
IIRC вы можете также \ избежать любого из символов разделителей для того же эффект: : <<\SKIP.
yyny
@zagpoint Это то место, откуда Python использует строки документации в качестве многострочных комментариев?
Sapphire_Brick
31

Если вы хотите обрезать файл до нуля байтов, что полезно для очистки журналов, попробуйте следующее:

:> file.log
Ахи тунец
источник
16
> file.logпроще и достигает того же эффекта.
амфетахин
55
Да, но счастливое лицо это то, что он делает для меня:>
Ахи Туна
23
@amphetamachine: :>более портативный. Некоторые оболочки (например, моя zsh) автоматически создают экземпляр cat в текущей оболочке и прослушивают stdin при перенаправлении без команды. Скорее cat /dev/null, :намного проще. Часто это поведение отличается в интерактивных оболочках, а не в сценариях, но если вы пишете сценарий таким образом, что он также работает в интерактивном режиме, отладка методом копирования-вставки будет намного проще.
Калеб
2
Чем : > fileотличается true > file(помимо количества персонажей и счастливого лица) в современной оболочке (при условии :и trueодинаково быстро)?
Адам Кац
30

Это похоже на passв Python.

Одно из применений - заглушить функцию, пока она не будет написана:

future_function () { :; }
Приостановлено до дальнейшего уведомления.
источник
29

Еще два использования, не упомянутые в других ответах:

логирование

Взять этот пример скрипта:

set -x
: Logging message here
example_command

Первая строка, set -xзаставляет оболочку распечатать команду перед ее выполнением. Это довольно полезная конструкция. Недостатком является то, что обычный echo Log messageтип заявления теперь печатает сообщение дважды. Метод двоеточия обходит это. Обратите внимание, что вам все равно придется экранировать специальные символы, как и для echo.

Название работы Cron

Я видел, как это используется в работе cron, вот так:

45 10 * * * : Backup for database ; /opt/backup.sh

Это задание cron, которое запускает скрипт /opt/backup.shкаждый день в 10:45. Преимущество этого метода заключается в том, что он лучше отображает темы электронной почты при /opt/backup.shвыводе некоторого результата.

Флимм
источник
Где находится местоположение журнала по умолчанию? Могу ли я установить местоположение журнала? Является ли целью больше создание выходных данных в stdout во время сценариев / фоновых процессов?
Домдамброджа
1
@domdambrogia При использовании set -xраспечатанные команды (включая что-то вроде : foobar) идут в stderr.
Flimm
23

Вы можете использовать его вместе с backticks ( ``) для выполнения команды без отображения ее вывода, например:

: `some_command`

Конечно, вы могли бы просто сделать some_command > /dev/null, но :-версия несколько короче.

При этом я бы не рекомендовал делать это на самом деле, потому что это могло бы просто сбить людей с толку. Это просто пришло на ум в качестве возможного варианта использования.

sepp2k
источник
25
Это небезопасно, если команда собирается сбросить несколько мегабайт выходных данных, поскольку оболочка буферизует выходные данные и затем передает их в качестве аргументов командной строки (пространство в стеке) в ':'.
Джулиано
15

Это также полезно для программ полиглот:

#!/usr/bin/env sh
':' //; exec "$(command -v node)" "$0" "$@"
~function(){ ... }

Это теперь и исполняемый скрипт оболочки и программа JavaScript: это означает ./filename.js, sh filename.jsиnode filename.js вся работа.

(Определенно немного странное использование, но, тем не менее, эффективно.)


Некоторые объяснения, как и требовалось:

  • Shell-скрипты оцениваются построчно; и execкоманда, когда выполняется, завершает оболочку и заменяет ее процесс результирующей командой. Это означает, что для оболочки программа выглядит так:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
  • Пока в слове не происходит расширения или псевдонима параметра, любое слово в сценарии оболочки может быть заключено в кавычки без изменения его значения; это означает, что ':'это эквивалентно :(мы только заключили это в кавычки здесь для достижения семантики JavaScript, описанной ниже)

  • ... и, как описано выше, первая команда в первой строке - это no-op (она переводит : //или, если вы предпочитаете заключать слова в кавычки, ':' '//'обратите внимание, что здесь //нет особого значения, как в JavaScript; это просто бессмысленное слово, которое выбрасывают.)

  • Наконец, вторая команда в первой строке (после точки с запятой) - это настоящая сущность программы: это execвызов, который заменяет вызываемый shell-скрипт с процессом Node.js, вызываемым для оценки остальной части скрипта.

  • Между тем, первая строка в JavaScript анализируется как string-literal ( ':'), а затем комментарий, который удаляется; Таким образом, для JavaScript программа выглядит так:

    ':'
    ~function(){ ... }

    Поскольку строковый литерал находится в строке сам по себе, он является оператором no-op и поэтому удаляется из программы; это означает, что вся строка удаляется, оставляя только ваш программный код (в данном примере, function(){ ... }тело.)

ELLIOTTCABLE
источник
Здравствуйте, вы можете объяснить, что : //;и ~function(){}делать? Спасибо:)
Стефан
1
@Stphane Добавил разбивку! Что касается ~function(){}, это немного сложнее. Здесь есть пара других ответов, которые касаются этого, хотя ни один из них не объясняет это, к моему удовлетворению… если ни один из этих вопросов не объясняет это достаточно хорошо для вас, не стесняйтесь разместить его здесь как вопрос, я буду с удовольствием подробно расскажу о новом вопросе.
ELLIOTTCABLE
1
Я не обратил внимание на node. Так что функциональная часть - это все о javascript! Я в порядке с унарным оператором перед IIFE. Я думал, что это тоже bash, и на самом деле не совсем понял смысл вашего поста. Я в порядке, спасибо за ваше время, потраченное на добавление «разбивки»!
Стефан
~{ No problem. (= }
ELLIOTTCABLE
12

Самодокументируемые функции

Вы также можете использовать :для встраивания документации в функцию.

Предположим, у вас есть библиотечный скрипт mylib.sh, предоставляющий множество функций. Вы можете либо получить библиотеку ( . mylib.sh) и вызывать функции непосредственно после этого ( lib_function1 arg1 arg2), либо избежать беспорядка в вашем пространстве имен и вызывать библиотеку с аргументом функции ( mylib.sh lib_function1 arg1 arg2).

Не было бы неплохо, если бы вы могли также набирать mylib.sh --helpи получать список доступных функций и их использование, без необходимости вручную поддерживать список функций в тексте справки?

#! / Bin / Баш

# все "публичные" функции должны начинаться с этого префикса
LIB_PREFIX = 'lib_'

# функции "публичной" библиотеки
lib_function1 () {
    : Эта функция делает что-то сложное с двумя аргументами.
    :
    : Параметры:
    : 'arg1 - первый аргумент ($ 1)'
    : 'arg2 - второй аргумент'
    :
    : Результат:
    : " это сложно"

    # фактический код функции начинается здесь
}

lib_function2 () {
    : Документация по функциям

    # код функции здесь
}

# функция помощи
--Помогите() {
    эхо MyLib v0.0.1
    эхо
    echo Использование: mylib.sh [имя_функции [args]]
    эхо
    эхо Доступные функции:
    объявить -f | sed -n -e '/ ^' $ LIB_PREFIX '/, / ^} $ / {/ \ (^' $ LIB_PREFIX '\) \ | \ (^ [\ t] *: \) / {
        s / ^ \ ('$ LIB_PREFIX'. * \) () / \ n === \ 1 === /; s / ^ [\ t] *: \? ['\' '"] \? / / ; s / [ '\' '"] \;? \ $ //,? р}}'
}

# основной код
if ["$ {BASH_SOURCE [0]}" = "$ {0}"]; затем
    # скрипт был выполнен вместо источника
    # вызвать запрошенную функцию или показать справку
    if ["$ (type -t -" $ 1 "2> / dev / null)" = function]; затем
        "$ @"
    еще
        --Помогите
    фи
фи

Несколько комментариев о коде:

  1. Все «публичные» функции имеют одинаковый префикс. Только они предназначены для вызова пользователем и перечислены в тексте справки.
  2. Функция самодокументирования опирается на предыдущий пункт и использует declare -fдля перечисления всех доступных функций, а затем фильтрует их через sed, чтобы отображать только функции с соответствующим префиксом.
  3. Рекомендуется заключать документацию в одинарные кавычки, чтобы избежать нежелательного расширения и удаления пробелов. Вы также должны быть осторожны при использовании апострофов / цитат в тексте.
  4. Вы можете написать код для интернализации префикса библиотеки, то есть пользователю нужно только ввести текст, mylib.sh function1и он будет переведен внутри lib_function1. Это упражнение оставлено читателю.
  5. Справочная функция называется «--help». Это удобный (то есть ленивый) подход, который использует механизм вызова библиотеки для отображения самой справки, без необходимости дополнительной проверки кода $1. В то же время, он будет загромождать ваше пространство имен, если вы создадите библиотеку. Если вам это не нравится, вы можете либо изменить имя на что-то похожее, lib_helpлибо фактически проверить аргументы --helpв основном коде и вызвать функцию справки вручную.
Сэр афон
источник
4

Я видел это использование в скрипте и думал, что это хорошая замена для вызова базового имени в скрипте.

oldIFS=$IFS  
IFS=/  
for basetool in $0 ; do : ; done  
IFS=$oldIFS  

... это замена для кода: basetool=$(basename $0)

Griff Derryberry
источник
Я предпочитаюbasetool=${0##*/}
Амит Найду