web-dev-qa-db-ja.com

:(コロン)GNU Bashビルトインの目的は何ですか?

コメントリーダーにすぎないが、実際にはシェル自体が組み込まれているコマンドの目的は何ですか?

スクリプトにコメントを挿入するよりも呼び出しごとに約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

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

295
amphetamachine

歴史的に、Bourneシェルには、組み込みコマンドとしてtrueおよびfalseがありませんでした。代わりにtrueは単に:にエイリアスされ、falselet 0のようなものにエイリアスされました。

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

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

ifには空でないthen句が必要であり、コメントは空ではないとカウントされないため、:はノーオペレーションとして機能します。

Nowadays(つまり、最新のコンテキストでは)通常、:またはtrueを使用できます。両方ともPOSIXで指定されており、一部はtrueが読みやすくなっています。ただし、興味深い違いが1つあります。:は、いわゆるPOSIX特殊なビルトインですが、trueです通常の組み込み

  • シェルに特別なビルトインを組み込む必要があります。通常のビルトインは「通常」のみビルトインされますが、厳密には保証されていません。通常、ほとんどのシステムのPATHにtrueの機能を持つ:という名前の通常のプログラムはありません。

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

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

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

  • 別の違いは、通常のビルトインはexecと互換性がなければならないことです。ここでは、Bashを使用して説明します。

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

366
earl

変数コマンドを簡単に有効/無効にするために使用します:

#!/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」の存在を保証する最も簡単な方法の1つですが、長さは0です。

57
Kevin Little

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

50
ormaaj

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

: << 'SKIP'

your code block here

SKIP
39
zagpoint

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

:> file.log
30
Ahi Tuna

Pythonのpassに似ています。

用途の1つは、記述されるまで関数をスタブすることです。

future_function () { :; }
27

他の回答で言及されていないさらに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が出力を出力するときに、電子メールの件名をより見やすくすることです。

25
Flimm

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

: `some_command`

もちろん、some_command > /dev/nullを実行することもできますが、:- versionは多少短くなります。

そうは言っても、人々を混乱させるだけなので、実際に行うことはお勧めしません。考えられるユースケースとして頭に浮かんだのです。

21
sepp2k

また、多言語プログラムにも役立ちます。

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

これは現在、実行可能なシェルスクリプトであるおよび JavaScriptプログラムです。つまり、./filename.jssh filename.js、およびnode filename.jsはすべて機能します。

(間違いなく少し奇妙な使い方ですが、それでも効果的です。)


要求されたいくつかの説明:

  • シェルスクリプトは行ごとに評価されます。 execコマンドを実行すると、シェルが終了し、replacesが結果のコマンドで処理されます。つまり、シェルから見ると、プログラムは次のようになります。

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
    
  • Wordでパラメーターの展開またはエイリアスが発生しない限り、anyシェルスクリプト内のWordは、その意味を変更せずに引用符で囲むことができます。これは、':':と同等であることを意味します(以下で説明するJavaScriptのセマンティクスを実現するために、ここでは引用符で囲んだだけです)

  • ...上記のように、最初の行の最初のコマンドはノーオペレーションです(: //に変換されます。または、単語を引用したい場合は':' '//'になります。//は、JavaScriptのようにここでは特別な意味を持ちません;それは捨てられている意味のないWordです。)

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

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

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

    文字列リテラルは単独で行にあるため、no-opステートメントであり、プログラムから削除されます。つまり、行全体が削除され、onlyがプログラムコード(この例ではfunction(){ ... }本体)のままになります。

13
ELLIOTTCABLE

自己文書化機能

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

さまざまな機能を提供するライブラリスクリプトmylib.shがあるとします。ライブラリをソース(. mylib.sh)し、その直後に関数を呼び出す(lib_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(){
:関数のドキュメント
 
#ここに関数コード
} 
 
#help function 
-help(){
 echo MyLib v0.0.1 
 echo 
 echo使用法:mylib.sh [function_name [args]] 
 echo 
 echo利用可能な関数:
 declare -f | sed -n -e '/ ^' $ LIB_PREFIX '/、/ ^} $/{/ \(^' $ LIB_PREFIX '\)\ | \(^ [\ t] *:\)/ {
 s/^ \( '$ LIB_PREFIX'。* \)()/\n ===\1 === /; s/^ [\ t] *:\?['\' '"] \?//; s/['\' '"] \?; \?$ //; p}}' 
} 
 
#main code 
 if [" $ {BASH_SOURCE [0]} "=" $ {0} "]; then 
#sourced 
の代わりにスクリプトが実行された#要求された機能を呼び出すか、ヘルプを表示
 if ["$(type -t-" $ 1 "2>/dev/null) "=関数]; then 
 "$ @" 
 else 
 --help 
 fi 
 fi 

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

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

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

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

...これはコードの代替です:basetool=$(basename $0)

3