:(コロン)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

私が本当に探しているのは、それが持っていたかもしれない歴史的なアプリケーションだと思います。



68
@Caleb-私はその2年前にこれを尋ねた。
amphetamachine

特定の値を返すコマンドが「何もしない」とは言いません。関数型プログラミングが「何もしない」ことで構成されていない限り。:-)
LarsH

回答:


415

歴史的に、Bourneシェルには組み込みコマンドはtrueありませんでしたfalsetrue代わりに、単に:やのfalseようなエイリアスになりましたlet 0

:true古代のボーン由来のシェルへの移植性よりもわずかに優れています。簡単な例として、!パイプライン演算子も||リスト演算子もないことを検討してください(一部のBourneシェルの場合と同様)。これelseにより、ifステートメントの句が終了ステータスに基づいて分岐する唯一の手段となります。

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

以来if空でない必要がthen句やコメントを非空としてカウントされません、:ノーオペレーションとして機能します。

現在(つまり、現代のコンテキストでは)、通常、:またはのいずれかを使用できますtrue。どちらもPOSIXで指定されており、true読みやすいものもあります。ただし、興味深い違いが1つあります。これ:は、いわゆるPOSIXの特別な組み込みであるのに対し、true通常の組み込みです。

  • シェルに組み込むには、特別な組み込みが必要です。通常のビルトインは「一般的に」組み込まれているだけですが、厳密に保証されているわけではありません。通常、ほとんどのシステムのPATH :機能で名前が付けられた通常のプログラムはありませんtrue

  • おそらく最も重要な違いは、特別なビルトインの場合、ビルトインによって設定された変数(単純なコマンド評価中の環境であっても)がksh93を使用して次のようにコマンドが完了した後も持続することです。

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

    Zshは、POSIX互換モードで動作している場合を除いて、GNU Bashと同様にこの要件を無視しますが、他のすべての主要な「POSIX sh派生」シェルは、ダッシュ、ksh93、およびmkshを含め、これを監視します。

  • もう1つの違いは、通常のビルトインと互換性がある必要があるexecことです。ここではBashを使用して説明します。

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIXはまた、これは実装固有の詳細ですが、:よりも高速である可能性があることを明示的に指摘していtrueます。


通常のビルトインと互換性があってはならないということexecですか?
Old Pro

1
@OldPro:いいえ、彼trueは通常のビルトインであるという点で正しいですが、ビルトインの代わりにexec使用しているという点で正しくありません/bin/true
追って通知があるまで一時停止。

1
@DennisWilliamson私は仕様が述べられている方法でただ行っていました。もちろん、通常のビルトインにはスタンドアロンバージョンも必要です。
ormaaj 2012年

17
+1すばらしい答え。: ${var?not initialized}et alのように、変数を初期化するための使用法に注意したいと思います。
tripleee 2014年

多かれ少なかれ無関係なフォローアップ。あなた:は特別な組み込みであり、それによって名前が付けられた関数を持っているべきではないと述べました。しかし、フォーク爆弾が:(){ :|: & };:名前で関数に名前を付ける最も一般的に見られる例ではありません:か?
Chong

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であることを保証する最も簡単な方法の1つです。


20
>afileさらにシンプルで、同じ効果が得られます。
2010

2
いいですね、この$ vechoトリックを使用して、維持しているスクリプトを簡略化します。
BarneySchmale 2015年

5
コロンを引用する利点は何vecho=":"ですか?読みやすさのためだけ?
leoj

56

:の便利なアプリケーションは、実際に結果をコマンドに渡すのではなく、副作用のためにパラメーター展開を使用することにのみ関心がある場合です。その場合、終了ステータスを0にするか1にするかによって、PEを:またはfalseへの引数として使用します: "${var:=$1}"。例はです。:は組み込みなので、かなり高速でなければなりません。


2
また、算術拡張の副作用のためにそれを使用することができます: $((a += 1))++--演算子はPOSIXに従って実装する必要はありません。)。bash、ksh、および可能な他のシェルでは、次のことも実行できます。((a += 1))または((a++))、POSIXでは指定されていません。
pabouk 2015年

@paboukはい、それ(())はすべて本当ですが、オプション機能として指定されています。「「((」で始まる文字シーケンスが「$」が前に付いている場合、シェルによって算術展開として解析される場合、「((式))」が算術式として評価される拡張を実装するシェルは、 "(("は、グループ化コマンドの代わりに算術評価として導入されています。 "
ormaaj

50

:ブロックのコメントにも使用できます(C言語の/ * * /と同様)。たとえば、スクリプト内のコードブロックをスキップする場合は、次のようにできます。

: << 'SKIP'

your code block here

SKIP

3
悪いアイデア。hereドキュメント内のコマンド置換は引き続き処理されます。
chepner 2014

33
そのような悪い考えではありません。区切り文字を単一引用符で囲むことにより、ここのドキュメントで変数の解決/置換を回避できます::<< 'SKIP'
Rondo

1
IIRC \ では、同じ効果を得るために、任意の区切り文字をエスケープすることもできます: <<\SKIP
yyny

31

ログをクリアするのに役立つように、ファイルをゼロバイトに切り捨てたい場合は、これを試してください:

:> file.log

16
> file.logよりシンプルで同じ効果が得られます。
amphetamachine

55
ああ、でも幸せそうな顔が私に何をしてくれるのか:>
Ahi Tuna

23
@amphetamachine::>よりポータブルです。一部のシェル(myなどzsh)は、現在のシェルで猫を自動インスタンス化し、コマンドなしでリダイレクトが与えられたときにstdinをリッスンします。むしろよりもcat /dev/null:はるかに簡単です。多くの場合、この動作はスクリプトではなく対話型シェルで異なりますが、対話型でも機能するようにスクリプトを作成すると、コピーと貼り付けによるデバッグがはるかに簡単になります。
カレブ

2
(文字数と幸せそうな顔を除いて)現代のシェル(と仮定し、同等に高速)とはどのように: > file違いますか?true > file:true
Adam Katz


29

他の回答で言及されていない2つの用途:

ロギング

次のスクリプト例を見てください。

set -x
: Logging message here
example_command

最初の行set -xは、シェルにコマンドを実行する前に出力させます。これは非常に便利な構成です。欠点は、通常のecho Log messageタイプのステートメントがメッセージを2回出力することです。コロン方式はそれを回避します。と同じように、特殊文字をエスケープする必要があることに注意してくださいecho

cronの役職

次のように、cronジョブで使用されているのを見てきました。

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

これは、/opt/backup.sh毎日10:45にスクリプトを実行するcronジョブです。この手法の利点は、/opt/backup.sh出力を印刷するときに電子メールの件名が見やすくなることです。


デフォルトのログの場所はどこですか?ログの場所を設定できますか?スクリプト/バックグラウンドプロセス中にstdoutに出力を作成する目的はありますか?
domdambrogia

1
@domdambrogiaを使用するset -xと、出力されたコマンド(など: foobar)はstderrに送られます。
Flimm

22

次のように、バックティック(``)と組み合わせて使用​​して、出力を表示せずにコマンドを実行できます。

: `some_command`

もちろんsome_command > /dev/null、単に実行することもでき:ますが、-versionはやや短いです。

とはいえ、人々を混乱させるだけなので、実際にそれを行うことはお勧めしません。考えられるユースケースとして思いついたばかりです。


25
シェルが出力をバッファーに入れてからコマンドライン引数(スタックスペース)として ':'に渡すため、コマンドが数メガバイトの出力をダンプする場合、これは安全ではありません。
ジュリアーノ

15

また、ポリグロットプログラムにも役立ちます。

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

これは、今、実行可能なシェルスクリプトの両方であるの意味:JavaScriptプログラム./filename.jssh filename.jsおよびnode filename.jsすべての作業。

(間違いなく少し変わった使い方ですが、それでも効果的です。)


要求に応じて、いくつかの説明:

  • シェルスクリプトは行ごとに評価されます。そして、execコマンド、実行は、シェルを終了し、置き換え結果コマンドでのプロセスを。つまり、シェルにとって、プログラムは次のようになります。

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
  • 長いなしパラメータ展開やエイリアシングなどの単語で発生しているとして、任意のシェル・スクリプト内の単語は、その意味を変えずに引用符でラップすることができます。これは、以下':'と同等です:(以下で説明するJavaScriptセマンティクスを実現するために、ここでは引用符で囲みました)。

  • ...そして上記のように、1行目の最初のコマンドはno-opです(これはに変換される: //か、単語を引用する場合は。JavaScriptのように':' '//'、に//は特別な意味がないことに注意してください。捨てられるのは意味のない言葉にすぎません。)

  • 最後に、最初の行の2番目のコマンド(セミコロンの後)は、プログラムの本当の意味です。execこれは、呼び出されるシェルスクリプトを、残りのスクリプトを評価するために呼び出されるNode.jsプロセスに置き換える呼び出しです。

  • 一方、JavaScriptの最初の行は文字列リテラル(':')として解析され、次にコメントが削除されます。したがって、JavaScriptの場合、プログラムは次のようになります。

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

    string-literalはそれ自体が1行にあるため、何もしないステートメントであり、プログラムから削除されます。つまり、行全体が削除され、プログラムコード(この例では本文)だけが残りますfunction(){ ... }


こんにちは、あなたは何を説明することができます: //;し、~function(){}やりますか?ありがとう:)
Stphane 2016

1
@Stphaneに内訳が追加されました!については~function(){}、少し複雑です。ありますカップルそれらのどれもが本当にここで問題としてそれを投稿すること自由に感じ、あなたのためだけでなく、十分にそれを説明してどちらもこれらの質問の場合、私はなります...私の満足度にそれを説明しないが、ここで他の回答は、その上にそのタッチは、新しい質問について詳しくお答えします。
ELLIOTTCABLE 2016

1
気をつけなかったnode。つまり、関数部分はすべてJavaScriptです。IIFEの前で単項演算子を使用しても問題ありません。そもそもこれはbashだと思いましたが、実際には投稿の意味があまりわかりませんでした。私はもう大丈夫です。«内訳»を追加していただきありがとうございます!
Stphane 2016

~{ No problem. (= }
ELLIOTTCABLE 2016

12

自己文書化機能

あなたも使うことができます :して、ドキュメントを関数に埋め込むます。

mylib.shさまざまな関数を提供するライブラリスクリプトがあるとします。ライブラリをソース. mylib.shlib_function1 arg1 arg2)し、その直後に関数を呼び出す()か、名前空間が乱雑になるのを避け、関数の引数でライブラリを呼び出す(mylib.sh lib_function1 arg1 arg2)ことができます。

mylib.sh --helpヘルプテキストの関数リストを手動で維持する必要なく、使用可能な関数とその使用法のリストを入力して取得できたら、すばらしいと思いませんか。

#!/ bin / bash

#すべての「パブリック」関数はこのプレフィックスで始まる必要があります
LIB_PREFIX = 'lib_'

#「パブリック」ライブラリ関数
lib_function1(){
    :この関数は、2つの引数で複雑な処理を行います。
    :
    : パラメーター:
    : 'arg1-最初の引数($ 1)'
    : 'arg2-2番目の引数'
    :
    :結果:
    : " それは複雑です"

    #実際の関数コードはここから始まります
}

lib_function2(){
    :関数のドキュメント

    #ここに関数コード
}

#ヘルプ機能
- 助けて() {
    MyLib v0.0.1をエコーし​​ます。
    エコー
    echo使用法: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)" = function]; その後
        「$ @」
    そうしないと
        - 助けて
    fi
fi

コードに関するいくつかのコメント:

  1. すべての「パブリック」関数には同じプレフィックスがあります。これらだけがユーザーによって呼び出され、ヘルプテキストにリストされることを意図しています。
  2. 自己文書化機能は前のポイントに依存し、使用します declare -f可能なすべての関数を列挙ためにし、sedを介してそれらをフィルタリングして、適切なプレフィックスを持つ関数のみを表示します。
  3. 不要な展開と空白の削除を防ぐために、ドキュメントを一重引用符で囲むことをお勧めします。テキストでアポストロフィ/引用符を使用する場合も注意が必要です。
  4. ライブラリプレフィックスを内部化するコードを記述できます。つまり、ユーザーは入力するだけでmylib.sh function1内部的にに変換されlib_function1ます。これは読者に任された演習です。
  5. ヘルプ機能の名前は「--help」です。これは、ライブラリ呼び出しメカニズムを使用してヘルプ自体を表示する便利な(つまり、遅延)アプローチであり、の追加のチェックをコーディングする必要はありません$1。同時に、ライブラリをソースにすると、ネームスペースが乱雑になります。それが気に入らない場合は、名前をのように変更するか、メインコードのlib_helpargsを実際に確認して--help、ヘルプ関数を手動で呼び出すことができます。

4

スクリプトでこの使用法を確認したところ、スクリプト内でbasenameを呼び出すのに適した代用だと思いました。

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

...これはコードの置き換えです: basetool=$(basename $0)


私が好むbasetool=${0##*/}
アミットナイドゥ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.