Яке призначення: (двокрапки) GNU Bash вбудований?


335

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

Це повільніше, ніж вставляти коментар у свої сценарії приблизно на 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

Я думаю, що я справді шукаю - це те, яке історичне застосування воно могло б мати.



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як це звичайний вбудований .

  • В оболонку потрібно вбудувати спеціальні вбудовані; Регулярні вбудовані вбудовані лише "типово", але це не суворо гарантується. Зазвичай не повинно бути звичайної програми, названої :з функцією trueв PATH більшості систем.

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

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

    Зауважте, що Zsh ігнорує цю вимогу, як і GNU Bash, за винятком випадків, коли вони працюють в режимі сумісності POSIX, але всі інші основні "POSIX sh похідні" оболонки дотримуються цього, включаючи тире, ksh93 та mksh.

  • Ще одна відмінність полягає в тому, що регулярні вбудовані модулі повинні бути сумісні з exec- продемонстровано тут за допомогою Bash:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIX також чітко зазначає, що це :може бути швидше true, хоча це, звичайно, конкретні деталі реалізації.


Ви мали на увазі, що регулярні вбудовані модулі не повинні відповідати exec?
Старий Про

1
@OldPro: Ні, він правильний у тому, що trueце звичайний вбудований, але він неправильний у тому, що execвикористовує /bin/trueзамість вбудованого.
Призупинено до подальшого повідомлення.

1
@DennisWilliamson Я просто йшов шляхом написання специфікації. Зрозуміло, що звичайний вбудований модуль також повинен мати окрему версію.
ormaaj

17
+1 Відмінна відповідь. Я все одно хотів би відзначити використання для ініціалізації змінних, наприклад, : ${var?not initialized}та ін.
трійчатка

Більш-менш споріднене спостереження. Ви сказали :, що це спеціальний вбудований і не повинен мати функцію, яку він назвав. Але хіба не найпоширеніший приклад того, як вилка бомба :(){ :|: & };:називає функцію ім'ям :?
Чон

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}". Так :як це вбудований, він повинен бути досить швидким.


2
Ви також можете використовувати його для побічних ефектів арифметичного розширення: : $((a += 1))( ++а --оператори не потрібно реалізовувати відповідно до POSIX.). У bash, ksh та інших інших оболонках ви також можете: ((a += 1))або, ((a++))але це не визначено POSIX.
пабук

@pabouk Так, це все правда, хоча (())вказана як додаткова функція. "Якщо послідовність символів, що починається з" (("буде розбиратися оболонкою як арифметичне розширення, якщо їй передує" $ ", оболонки, які реалізують розширення, за допомогою яких" ((вираз)) "оцінюється як арифметичний вираз the "((" як введення як арифметичне оцінювання замість групування команди. "
ormaaj

50

:також може бути для блокування коментарів (схоже на / * * / на мові C). Наприклад, якщо ви хочете пропустити блок коду в своєму сценарії, ви можете зробити це:

: << 'SKIP'

your code block here

SKIP

3
Погана ідея. Будь-які заміни команд усередині документа тут все ще обробляються.
чепнер

33
Не така погана ідея. Ви можете уникнути змінної роздільної здатності / заміни в документах тут, котируючи роздільник:: << 'SKIP'
Рондо

1
IIRC ви можете також \ уникнути будь-якого з символів роздільників для того ж ефект: : <<\SKIP.
yyny

@zagpoint Чи тут Python використовує docstrings як багаторядкові коментарі?
Sapphire_Brick

31

Якщо ви хочете обрізати файл до нуля байтів, корисного для очищення журналів, спробуйте:

:> file.log

16
> file.logпростіше і досягає того ж ефекту.
амфетамахін

55
Так, але щасливе обличчя - це те, що для мене це:>
Ахі Туна,

23
@amphetamachine: :>більш портативний. Деякі оболонки (такі як моя zsh) автоматично ініціюють кота в поточній оболонці і слухають stdin, коли йому надано переспрямування без команди. Замість того cat /dev/null, :набагато простіше. Часто така поведінка відрізняється в інтерактивних оболонках, а не в сценаріях, але якщо ви пишете сценарій таким чином, що також працює в інтерактивному режимі, налагодження методом copy-paste набагато простіше.
Калеб

2
Чим : > fileвідрізняється від true > file(окрім числа персонажів та щасливого обличчя) сучасної оболонки (якщо вважати :і trueоднаково швидкою)?
Адам Кац


29

Ще два способи використання, не вказані в інших відповідях:

Ведення журналів

Візьмемо цей приклад сценарію:

set -x
: Logging message here
example_command

Перший рядок, set -xзмушує оболонку роздрукувати команду перед її запуском. Це досить корисна конструкція. Мінусом є те, що звичайний echo Log messageтип оператора тепер друкує повідомлення двічі. Метод товстої кишки обходить це. Зауважте, що вам все одно доведеться уникати спеціальних символів так само, як і раніше echo.

Назви роботи Cron

Я бачив, як він використовується в роботі з cron, як ось такий:

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

Це робота з крон, яка виконує сценарій /opt/backup.shщодня о 10:45. Перевага цієї методики полягає в тому, що вона дозволяє покращити вигляд предметів електронної пошти, коли /opt/backup.shдрукується деякий вихід.


Де розташування журналу за замовчуванням? Чи можна встановити розташування журналу? Чи більше призначення для створення виводу в stdout під час сценаріїв / фонових процесів?
domdambrogia

1
@domdambrogia При використанні set -xроздруковані команди (включаючи щось на зразок : foobar) переходять до stderr.
Flimm

22

Ви можете використовувати його спільно з backticks ( ``) для виконання команди, не показуючи її вихід, наприклад:

: `some_command`

Звичайно, ви могли просто зробити some_command > /dev/null, але :-версія дещо коротша.

Попри це, я не рекомендую насправді робити це, оскільки це просто заплутає людей. Це просто прийшло в голову як можливий випадок використання.


25
Це не є безпечним, якщо команда скидає кілька мегабайт виводу, оскільки оболонка буферує висновок, а потім передає його як аргументи командного рядка (простір стека) до ':'.
Джуліано

15

Це також корисно для поліглот-програм:

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

Це тепер і виконуваний скрипт оболонки і програма JavaScript: це означає ./filename.js, sh filename.jsі node filename.jsвся робота.

(Очевидно, трохи дивного використання, але, тим не менш, ефективного.)


Деякі пояснення на вимогу:

  • Сценарії оболонок оцінюються по черзі; і execкоманда, коли виконується, припиняє оболонку і замінює її процес результативною командою. Це означає, що для оболонки програма виглядає так:

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

  • ... і як описано вище, перша команда в першому рядку є неоперативною (це означає : //, або якщо ви віддаєте перевагу цитувати слова ':' '//'.) Зауважте, що тут //не має особливого значення, як це робиться в JavaScript; це просто безглузде слово, яке викидається.)

  • Нарешті, друга команда в першому рядку (після крапки з комою) - це справжнє м'ясо програми: це execвиклик, який замінює виклик скрипту оболонки , а процес Node.js викликається для оцінки решти сценарію.

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

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

    Оскільки рядок-літерал знаходиться на рядку сам по собі, він є неоперативним оператором, і таким чином вилучається з програми; це означає, що весь рядок видалено, залишивши лише ваш код програми (у цьому прикладі function(){ ... }корпус.)


Привіт, ти можеш пояснити, що : //;і ~function(){}робити? Дякую:)
Stphane

1
@Stphane Додано розбиття! Щодо ~function(){}, це трохи складніше. Тут є кілька інших відповідей, які стосуються цього, хоча жоден з них не пояснює це мені на задоволення… якщо жодне з цих питань не пояснює це досить добре для вас, не соромтесь написати це як запитання тут, я буду із задоволенням відповімо поглиблено на нове запитання.
ELLIOTTCABLE

1
Я не звертав уваги node. Отже, функціональна частина стосується javascript! Я в порядку з одинарним оператором перед IIFE. Я подумав, що це в першу чергу теж башти і насправді не отримав сенсу вашої посади. Зараз я добре, дякую за витрачений час на додавання «розбивки»!
Stphane

~{ No problem. (= }
ELLIOTTCABLE

12

Функції самодокументування

Ви також :можете вбудувати документацію у функцію.

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

Чи не було б непогано, якби ви також могли набрати mylib.sh --helpта отримати список доступних функцій та їх використання, не потребуючи вручну підтримувати список функцій у тексті довідки?

#! / бін / баш

# усі "загальнодоступні" функції повинні починатися з цього префікса
LIB_PREFIX = 'lib_'

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

    # фактичний код функції починається тут
}

lib_function2 () {
    : Функціональна документація

    # код функції тут
}

# довідкова функція
--help () {
    echo MyLib v0.0.1
    відлуння
    Використання відлуння: mylib.sh [function_name [args]]
    відлуння
    echo Доступні функції:
    оголосити -f | sed -n -e '/ ^' $ LIB_PREFIX '/, / ^} $ / {/ \ (^' $ LIB_PREFIX '\) \ | \ (^ [\ t] *: \) / {
        s / ^ \ ('$ LIB_PREFIX'. * \) () / \ n === \ 1 === /; s / ^ [\ t] *: \? ['\' '"] \? / / ; s / ['\' '"] \?; \? $ //; p}}'
}

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

Кілька коментарів щодо коду:

  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)


Я віддаю перевагуbasetool=${0##*/}
Аміт Найду
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.