Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 12236] ファイルの概要

このコミットは、Go言語の公式リポジトリにおける、misc/bash/go ファイルの追加に関するものです。このファイルは、Bashシェルにおけるgoコマンドのコマンドライン補完機能を提供します。これにより、ユーザーはgoコマンドとそのサブコマンド、および関連する引数をより効率的に入力できるようになります。

コミット

commit 1f5fde09159fe2a4a87129af307045ca7b12f727
Author: Yissakhar Z. Beck <yissakhar.beck@gmail.com>
Date:   Tue Feb 28 07:41:49 2012 +1100

    misc/bash: Completion for go tool.

    This covers most of the tool's functionality. At some point,
    support should probably be added for testflags and the various go
    tools.

    R=golang-dev, bradfitz, kyle, minux.ma
    CC=golang-dev
    https://golang.org/cl/5646066

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/1f5fde09159fe2a4a87129af307045ca7b12f727

元コミット内容

このコミットは、goツールのBash補完機能を追加します。 これはツールのほとんどの機能をカバーしていますが、将来的にはtestflagsや様々なgoツールに対するサポートも追加されるべきです。

変更の背景

コマンドラインインターフェース(CLI)を使用する開発者にとって、コマンドの入力補完は生産性を大幅に向上させる重要な機能です。特に、goコマンドのように多くのサブコマンドやオプションを持つツールの場合、手動での入力は手間がかかり、タイプミスを誘発しやすくなります。

このコミットの背景には、Go開発者がよりスムーズにgoコマンドを操作できるようにするという目的があります。Bash補完機能を提供することで、ユーザーはTabキーを押すだけで利用可能なサブコマンドやファイルパス、インポートパスなどを自動的に補完できるようになり、開発ワークフローが効率化されます。これにより、Go言語のツールチェインの使いやすさが向上し、開発者の負担が軽減されます。

前提知識の解説

Bash (Bourne-Again SHell)

Bashは、Unix系オペレーティングシステムで広く利用されているシェル(コマンドラインインタープリタ)です。ユーザーが入力したコマンドを解釈し、実行する役割を担います。多くのLinuxディストリビューションやmacOSでデフォルトのシェルとして採用されています。

コマンドライン補完 (Tab Completion)

コマンドライン補完は、ユーザーがコマンドの一部を入力した際に、Tabキーを押すことで残りの部分を自動的に補完する機能です。これにより、コマンド名、ファイル名、ディレクトリ名、コマンドの引数などを効率的に入力できます。Bashでは、completeコマンドやcompgenコマンド、そして特定のシェル関数を組み合わせてこの機能を実現します。

complete コマンド

Bashの組み込みコマンドで、特定のコマンドに対する補完ルールを定義するために使用されます。

  • -F function_name: 指定されたコマンドの補完にfunction_nameというシェル関数を使用することを指定します。この関数が補完候補を生成します。
  • -o filenames: ファイル名の補完を有効にします。
  • command_name: 補完ルールを適用するコマンドの名前。

compgen コマンド

compgenは、補完候補を生成するためのBashの組み込みコマンドです。

  • -W wordlist: スペースで区切られた単語リストから補完候補を生成します。
  • -- string: stringで始まる単語のみをフィルタリングします。

_get_cword 関数

Bash補完スクリプトでよく使われるヘルパー関数で、現在のカーソル位置にある単語のインデックス(COMP_CWORD)を取得します。

COMP_WORDSCOMP_CWORD

Bashの補完機能が呼び出された際に設定される特殊なシェル変数です。

  • COMP_WORDS: 現在のコマンドラインを単語の配列として格納します。
  • COMP_CWORD: COMP_WORDS配列の中で、現在カーソルがある単語のインデックスを示します。

_filedir 関数

Bash補完スクリプトで一般的に使用されるヘルパー関数で、ファイル名やディレクトリ名を補完候補として提供します。引数に拡張子を指定することで、特定の拡張子のファイルのみを補完対象とすることも可能です。

go list all

go listコマンドはGoパッケージに関する情報を表示します。go list allは、Go環境で利用可能なすべてのパッケージのインポートパスをリストアップします。この情報は、補完機能でGoのインポートパスを提案する際に非常に有用です。

技術的詳細

このコミットで追加されたmisc/bash/goスクリプトは、Bashのプログラマブル補完機能を利用してgoコマンドの補完を実現しています。スクリプトの主要な部分は以下の通りです。

  1. _go_importpath() 関数: この関数は、go list allコマンドの出力と、allstdといった一般的なGoのインポートパスを組み合わせて、補完候補となるGoのインポートパスを生成します。compgen -Wを使用して、現在の入力文字列に一致するパスをフィルタリングします。

  2. _go() 関数: このスクリプトの核となる関数で、goコマンドの補完ロジックを実装しています。

    • _get_cwordを使用して現在のカーソル位置の単語インデックスを取得し、COMP_WORDS配列から現在のコマンドと直前の単語を特定します。
    • cmds変数には、goコマンドの主要なサブコマンド(build, clean, doc, fix, fmt, get, install, list, run, test, tool, version, vet)が定義されています。
    • addhelp変数には、helpコマンドの引数として利用できる追加のキーワード(gopath, importpath, remote, testflag, testfunc)が定義されています。
    • COMP_CWORDが1の場合(つまり、goコマンドの直後にサブコマンドを入力しようとしている場合)、cmdsリストからサブコマンドを補完します。
    • 各サブコマンド(build, clean, doc, fix, fmt, get, install, list, run, test, tool, vet, help)に対して、case文を用いて個別の補完ロジックが実装されています。
      • オプションの補完: -a, -n, -oなどのオプションはcompgen -Wを使って補完されます。
      • ファイルパスの補完: _filedir関数を使って.goファイルやその他のファイルパスが補完されます。
      • インポートパスの補完: _go_importpath関数を使ってGoのインポートパスが補完されます。
      • go buildの特殊なロジック: .goファイルが既に引数として指定されているか、インポートパスが指定されているかによって、ファイルパスとインポートパスのどちらを補完するかを切り替える複雑なロジックが含まれています。
      • go toolの補完: go toolコマンドのサブコマンドは、go toolコマンド自体の出力を解析して動的に取得されます。
    • TODOコメントが多数含まれており、testflagsや特定のgo toolサブコマンド(例: 568a, 568c, api, cgoなど)に対するより詳細な補完機能が将来的に追加されるべきであることが示されています。
  3. complete -F _go go: この行は、goコマンドに対して、補完機能が要求された際に_go関数を呼び出すようにBashに指示します。$filenamesオプションは、デフォルトのファイル名補完も有効にすることを意味します。

このスクリプトは、Bashの強力なプログラマブル補完フレームワークを活用し、Go開発者にとって非常に便利な機能を提供しています。

コアとなるコードの変更箇所

このコミットでは、misc/bash/goという新しいファイルが追加されています。このファイル全体が、goコマンドのBash補完機能の実装です。

--- /dev/null
+++ b/misc/bash/go
@@ -0,0 +1,247 @@
+complete -f -X '!*.8' 8l
+complete -f -X '!*.6' 6l
+complete -f -X '!*.5' 5l
+complete -f -X '!*.go' 8g 6g 5g gofmt gccgo
+
+_go_importpath()
+{
+  echo "$(compgen -W "$(go list all) all std" -- "$1")"
+}
+
+_go()
+{
+  # TODO: Only allow flags before other arguments. run already does
+  # this.
+
+  local cur=`_get_cword`
+  local prev="${COMP_WORDS[COMP_CWORD-1]}"
+
+  local cmd="${COMP_WORDS[1]}"
+
+  local cmds="build clean doc fix fmt get
+    install list run test tool version vet"
+  local addhelp="gopath importpath remote
+    testflag testfunc"
+  local other="help"
+
+  if [ "$COMP_CWORD" == 1 ]; then
+    for opt in $cmds; do
+      if [[ "$opt" == "$cmd" ]]; then
+        COMPREPLY=("$opt")
+        return
+      fi
+    done
+  fi
+
+  case "$cmd" in
+    'build')
+      case "$prev" in
+        '-o')
+          _filedir
+          ;;
+        '-p')
+          ;;
+        *)
+          if [[ "$cur" == -* ]]; then
+            COMPREPLY=($(compgen -W "-a -n -o -p -v -x" -- "$cur"))
+          else
+            local found=0
+            for ((i=0; i < ${#COMP_WORDS[@]}; i++)); do
+              case "$i" in
+                0|1|"$COMP_CWORD")
+                  continue
+                  ;;
+              esac
+              local opt="${COMP_WORDS[i]}"
+              if [[ "$opt" != -* ]]; then
+                if [[ "$opt" == *.go && -f "$opt" ]]; then
+                  found=1
+                  break
+                else
+                  found=2
+                  break
+                fi
+              fi
+            done
+            case "$found" in
+              0)
+                _filedir go
+                COMPREPLY+=(`_go_importpath "$cur"`)\
+                ;;
+              1)
+                _filedir go
+                ;;
+              2)
+                COMPREPLY=(`_go_importpath "$cur"`)\
+                ;;
+            esac
+          fi
+          ;;
+      esac
+      ;;
+    'clean')
+      if [[ "$cur" == -* ]]; then
+        COMPREPLY=($(compgen -W "-i -r -n -x" -- "$cur"))
+      else
+        COMPREPLY=(`_go_importpath "$cur"`)\
+      fi
+      ;;
+    'doc')
+      COMPREPLY=(`_go_importpath "$cur"`)\
+      ;;
+    'fix')
+      COMPREPLY=(`_go_importpath "$cur"`)\
+      ;;
+    'fmt')
+      COMPREPLY=(`_go_importpath "$cur"`)\
+      ;;
+    'get')
+      case "$prev" in
+        '-p')
+          ;;
+        *)
+          if [[ "$cur" == -* ]]; then
+            COMPREPLY=($(compgen -W "-a -d -fix -n -p -u -v -x" -- "$cur"))
+          else
+            COMPREPLY=(`_go_importpath "$cur"`)\
+          fi
+          ;;
+      esac
+      ;;
+    'install')
+      case "$prev" in
+        '-p')
+          ;;
+        *)
+          if [[ "$cur" == -* ]]; then
+            COMPREPLY=($(compgen -W "-a -n -p -v -x" -- "$cur"))
+          else
+            COMPREPLY=(`_go_importpath "$cur"`)\
+          fi
+          ;;
+      esac
+      ;;
+    'list')
+      case "$prev" in
+        '-f')
+          ;;
+        *)
+          if [[ "$cur" == -* ]]; then
+            COMPREPLY=($(compgen -W "-e -f -json" -- "$cur"))
+          else
+            COMPREPLY=(`_go_importpath "$cur"`)\
+          fi
+          ;;
+      esac
+      ;;
+    'run')
+      if [[ "$cur" == -* && "$prev" != *.go ]]; then
+        COMPREPLY=($(compgen -W "-a -n -x" -- "$cur"))
+      else
+        _filedir
+      fi
+      ;;
+    'test') # TODO: Support for testflags.
+      case "$prev" in
+        '-file')
+          _filedir go
+          ;;
+        '-p')
+          ;;
+        *)
+          if [[ "$cur" == -* ]]; then
+            COMPREPLY=($(compgen -W "-c -file -i -p -x" -- "$cur"))
+          else
+            COMPREPLY=(`_go_importpath "$cur"`)\
+          fi
+          ;;
+        esac
+      ;;
+    'tool')
+      if [ "$COMP_CWORD" == 2 ]; then
+        COMPREPLY=($(compgen -W "$(go tool)" -- "$cur"))
+      else
+        case "${COMP_WORDS[2]}" in
+          [568]a) # TODO: Implement something.
+            #_go_tool_568a
+            ;;
+          [568]c) # TODO: Implement something.
+            #_go_tool_568c
+            ;;
+          [568]g) # TODO: Implement something.
+            #_go_tool_568g
+            ;;
+          [568]l) # TODO: Implement something.
+            #_go_tool_568l
+            ;;
+          'api') # TODO: Implement something.
+            #_go_tool_api
+            ;;
+          'cgo') # TODO: Implement something.
+            #_go_tool_cgo
+            ;;
+          'cov') # TODO: Implement something.
+            #_go_tool_cov
+            ;;
+          'dist') # TODO: Implement something.
+            #_go_tool_dist
+            ;;
+          'ebnflint') # TODO: Implement something.
+            #_go_tool_ebnflint
+            ;;
+          'fix') # TODO: Implement something.
+            #_go_tool_fix
+            ;;
+          'gotype') # TODO: Implement something.
+            #_go_tool_gotype
+            ;;
+          'nm') # TODO: Implement something.
+            #_go_tool_nm
+            ;;
+          'pack') # TODO: Implement something.
+            #_go_tool_pack
+            ;;
+          'pprof') # TODO: Implement something.
+            #_go_tool_pprof
+            ;;
+          'prof') # TODO: Implement something.
+            #_go_tool_prof
+            ;;
+          'vet') # TODO: Implement something.
+            #_go_tool_vet
+            ;;
+          'yacc') # TODO: Implement something.
+            #_go_tool_yacc
+            ;;
+        esac
+        if [[ "$cur" == -* ]]; then
+          COMPREPLY=($(compgen -W "${COMPREPLY[*]} -h" -- "$cur"))
+        fi
+      fi
+      ;;
+    'version')
+      ;;
+    'vet')
+      if [[ "$cur" == -* ]]; then
+        :
+      else
+        COMPREPLY=(`_go_importpath "$cur"`)\
+      fi
+      ;;
+    'help')
+      if [ "$COMP_CWORD" == 2 ]; then
+        COMPREPLY=($(compgen -W "$cmds $addhelp" -- "$cur"))
+      fi
+      ;;
+    *)
+      if [ "$COMP_CWORD" == 1 ]; then
+        COMPREPLY=($(compgen -W "$cmds $other" -- "$cur"))
+      else
+        _filedir
+      fi
+      ;;
+  esac
+}
+
+complete $filenames -F _go go
+
+# vim:ts=2 sw=2 et syn=sh

コアとなるコードの解説

追加されたmisc/bash/goファイルは、Bashのプログラマブル補完機能を利用してgoコマンドの補完を提供します。

  1. 初期設定: ファイルの冒頭では、complete -f -Xコマンドがいくつか記述されています。これらは、特定のコマンド(例: 8l, 6l, 5l, 8g, 6g, 5g, gofmt, gccgo)に対して、特定の拡張子(例: .8, .6, .5, .go)を持つファイルを補完対象から除外する設定を行っています。これは、これらのコマンドが通常、特定の種類のファイル(例: アセンブリファイルやGoソースファイル)を引数として取るため、それ以外のファイルを補完候補として表示しないようにするためのものです。

  2. _go_importpath() 関数:

    _go_importpath()
    {
      echo "$(compgen -W "$(go list all) all std" -- "$1")"
    }
    

    この関数は、Goのパッケージインポートパスを補完するためのものです。

    • go list all: 現在のGo環境で利用可能なすべてのパッケージのインポートパスをリストアップします。
    • all std: all(すべてのパッケージ)とstd(標準ライブラリパッケージ)という特別なキーワードも補完候補に含めます。
    • compgen -W "..." -- "$1": 上記のリストと、現在の入力文字列($1)を比較し、一致する補完候補を生成します。これにより、ユーザーがGoのパッケージ名を途中まで入力した際に、関連するインポートパスが提案されます。
  3. _go() 関数: この関数がgoコマンドの実際の補完ロジックを実装しています。

    • local cur=_get_cword``: 現在カーソルがある単語を取得します。
    • local prev="${COMP_WORDS[COMP_CWORD-1]}": 直前の単語を取得します。
    • local cmd="${COMP_WORDS[1]}": goコマンドの直後のサブコマンド(例: build, testなど)を取得します。
    • cmds, addhelp, other変数には、goコマンドの主要なサブコマンドやヘルプオプションが定義されています。
    • サブコマンドの補完: if [ "$COMP_CWORD" == 1 ]のブロックでは、ユーザーがgoと入力した直後にTabキーを押した場合に、利用可能なサブコマンド(build, cleanなど)を補完します。
    • case "$cmd" in ... esac ブロック: この大きなcase文は、goコマンドの各サブコマンド(build, clean, doc, get, install, list, run, test, tool, vet, help)に対して、それぞれ異なる補完ロジックを適用します。
      • オプションの補完: 多くのサブコマンドでは、-a, -n, -vなどの共通オプションや、サブコマンド固有のオプション(例: go build -o)がcompgen -Wを使って補完されます。
      • ファイルパスの補完: _filedir関数が使用され、現在のディレクトリ内のファイルやディレクトリが補完候補として提供されます。_filedir goのように引数を指定することで、.goファイルのみを補完対象とすることも可能です。
      • インポートパスの補完: _go_importpath "$cur"が呼び出され、Goのインポートパスが補完されます。
      • go buildの複雑なロジック: buildサブコマンドの補完は特に複雑です。既に.goファイルが引数として指定されているか、またはインポートパスが指定されているかによって、ファイルパスとインポートパスのどちらを優先して補完するかを判断します。これは、go buildがファイルパスもインポートパスも引数として受け取るためです。
      • go toolの動的な補完: toolサブコマンドの場合、go toolコマンド自体の出力を実行し、その結果をcompgen -Wに渡すことで、利用可能なツール名を動的に補完します。これにより、Goのバージョンアップによって新しいツールが追加された場合でも、補完スクリプトを更新することなく対応できます。
      • TODOコメント: スクリプト内には多くのTODOコメントがあり、testflagsや特定のgo toolサブコマンド(例: 568a, apiなど)に対するより詳細な補完機能が将来的に追加されるべきであることが示されています。これは、このコミットが補完機能の初期実装であり、今後の拡張の余地があることを示唆しています。
  4. complete $filenames -F _go go:

    complete $filenames -F _go go
    

    この行は、Bashに対して、goコマンドの補完を行う際に_go関数を呼び出すように登録します。$filenamesは、デフォルトのファイル名補完も有効にすることを意味します。これにより、ユーザーがgoコマンドを入力し、Tabキーを押すと、_go関数が実行され、適切な補完候補が提供されるようになります。

このスクリプトは、Bashのプログラマブル補完の強力な機能を活用し、Go開発者にとって非常に便利なコマンドライン体験を提供するための基盤を築いています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Bashの公式マニュアル
  • Go言語のソースコード(特にcmd/goディレクトリ)
  • 一般的なBash補完スクリプトの慣習と実装例