📻

awkでdocker psのID/NAMESを抜き取る

2020/09/25に公開

dockerで諸操作を行う際に、docker psを実行してコンテナ状態を確認することがあると思います。
そこで表示された結果のうち、操作したいコンテナIDやコンテナ名を
マウスでコピーしてdockerの停止・削除を行っている人は意外といるのではないでしょうか。

docker psの出力にawkを組み合わせることにより、マウスでコピーしなくともコンテナ操作が出来るようになります。
ID/NAMES列を抜き出す方法は1通りではないのですが、この記事では私が考えうる限り最も楽な方法を説明します。

この記事の最も重要なポイントは、「awk '$0=$1'は便利!」です。
といっても特別awkに詳しくない限り一気に飛躍し過ぎなため、順を追って見ていきます。
既に各々のやり方が浮かんでいる人は参考程度にご覧下さい。

docker psのサンプル

本記事で使うコンテナの状態を例示します(私の実環境を元に少しだけ加工したものです)

$ docker ps -a
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                      PORTS                            NAMES
DG0da5099539        bashcms2:test         "apachectl -D FOREGR…"   2 weeks ago         Exited (255) 11 days ago    8070/tcp, 0.0.0.0:8070->80/tcp   bashcms2
DGd65fa33a85        c391afa02765          "/bin/sh -c 'target=…"   2 weeks ago         Exited (127) 2 weeks ago                                     busy_beaver
DG1de459c644        ubuntu                "/bin/bash"              2 months ago        Exited (130) 2 months ago                                    hungry_bhaskara
DG99f3eb9712        growi_elasticsearch   "/usr/local/bin/dock…"   6 months ago        Up 11 days                  9200/tcp, 9300/tcp               growi_elasticsearch_1

この出力から、特定のコンテナのIDやコンテナ名をawkで取得していきます。

そもそもawkとは

テキストの入力の他にパターンアクションを指定するのがawkの基本的な使い方です。
入力されたテキストのうち、特定のパターンに合致する行に特定のアクションを行うことにより、テキストの加工を行うことが出来ます。

awk 'パターン{アクション}' 入力データ
https://www.gnu.org/software/gawk/manual/gawk.html#Getting-Started

awkを本格的に使う場合、パターンには条件式や正規表現を、アクションには制御文や組み込み関数を組み合わせると物凄いことになるのですがこの記事ではごく一部に限定します。

以下は、テキスト入力(unameの出力)から特定の列をawkで表示する例になります。

$ uname -a
Linux raspberrypi 4.19.66-v7+ #1253 SMP Thu Aug 15 11:49:46 BST 2019 armv7l GNU/Linux

$ uname -a | awk '{print $2}'  # 2列目を表示
raspberrypi

$ uname -a | awk '{print $NF}'  # 最終列を表示 (NF = Number of Fields、要素数)
GNU/Linux

$ uname -a | awk '{print $0}'  # 全列を表示
Linux raspberrypi 4.19.66-v7+ #1253 SMP Thu Aug 15 11:49:46 BST 2019 armv7l GNU/Linux

awkでは、入力されたテキストを空白シーケンス(1つ以上のスペース、タブ、改行)で区切った要素で扱うことが出来ます。
$2と書くと空白スペース区切りの2番目を、$0と書くとテキストの区切り関係無く行全体を指定することが出来ます。

https://www.gnu.org/software/gawk/manual/gawk.html#Default-Field-Splitting
https://www.gnu.org/software/gawk/manual/gawk.html#index-sidebar-6

コンテナID一覧、コンテナ名一覧を取得する

ここまでの内容を踏まえると、docker psの結果からコンテナID・コンテナ名を抜き出せるようになります。

# コンテナID一覧を取得(1列目)
$ docker ps -a | awk '{print $1}'
CONTAINER
DG0da5099539
DGd65fa33a85
DG1de459c644
DG99f3eb9712

# コンテナ名取得(最終列)
$ docker ps -a | awk '{print $NF}'
NAMES
bashcms2
busy_beaver
hungry_bhaskara
growi_elasticsearch_1

# ubuntuイメージのコンテナIDだけ取得
$ docker ps -a | grep ubuntu | awk '{print $1}'
DG1de459c644

一覧を取得する場合はラベル行(CONTAINER,NAMES)が表示されていますが、特定のコンテナを指定する場合はそのコンテナの元となるイメージ名やコンテナ名でgrepするため、ラベル行を取り除くことが出来ます。

コンテナIDが取得出来たら、次のように停止・削除などが出来ます。

# コンテナを停止
$ docker stop $(docker ps -a | grep <対象コンテナの名前・ID> | awk '{print $1}') 

# コンテナを削除
$ docker rm $(docker ps -a | grep <対象コンテナの名前・ID> | awk '{print $1}') 

# コンテナの中に入る
$ docker exec -it $(docker ps -a | grep <対象コンテナの名前・ID> | awk '{print $1}') /bin/bash

awk表記をもっと短くする

当初の目的は果たせましたが、awkの記述はもう少し短く出来ます。
ここでの目的は中括弧を書かずに特定列を表示することです。

アクションを省略する

# (再掲)awkの普通のアクション表記
$ uname -a | awk '{print $0}' 
Linux raspberrypi 4.19.66-v7+ #1253 SMP Thu Aug 15 11:49:46 BST 2019 armv7l GNU/Linux

# パターンに1を指定し、アクションを省略する = print $0と同等
$ uname -a | awk '1'  
Linux raspberrypi 4.19.66-v7+ #1253 SMP Thu Aug 15 11:49:46 BST 2019 armv7l GNU/Linux

# パターンが0の場合、アクションが実行されない
$ uname -a | awk '0' 
$ 

awkの基本的な書き方はawk 'パターン{アクション}' 入力データなのですが、アクションを指定しない場合{print $0}がデフォルトで適用されます。
https://www.gnu.org/software/gawk/manual/gawk.html#Action-Overview

ただし、そのアクションが実行されるのはパターンにマッチした場合のみです。
1だけ書くと式として解釈され、文字列は空文字以外・数字は0以外であればマッチしたと解釈されます。
https://www.gnu.org/software/gawk/manual/gawk.html#Pattern-Overview

$0を上書きする

これで、awk '1'と書くと全行がそのまま表示される(catと同じ動きをする)ことが分かりました。後は特定の列だけ表示する処理です。

通常はアクションで表示したい列を指定するのですが、$0を置き換える式をパターンに書くことによりアクションを書かずして指定した行のみ表示することが出来ます。
https://www.gnu.org/software/gawk/manual/gawk.html#Changing-Fields

# 1列目のみ表示
$ uname -a | awk '$0=$1'
Linux

# 最終列のみ表示
$ uname -a | awk '$0=$NF'
GNU/Linux

これで中括弧を書かずに特定列を表示することが出来るようになりました。
先ほどのコンテナID取得・コンテナ名の取得方法を書き換えてみましょう。

# コンテナID一覧を取得(1列目)
$ docker ps -a | awk '$0=$1'
CONTAINER
DG0da5099539
DGd65fa33a85
DG1de459c644
DG99f3eb9712

# コンテナ名取得(最終列)
$ docker ps -a | awk '$0=$NF'
NAMES
bashcms2
busy_beaver
hungry_bhaskara
growi_elasticsearch_1

# ubuntuイメージのコンテナIDだけ取得
$ docker ps -a | grep ubuntu | awk '$0=$1'
DG1de459c644

普段awkに慣れていない人でも、この程度の長さなら使えそうではないでしょうか?

注意点

この記事のタイトル、本当は「awkでdocker psの欲しい列を抜き取る」としたかったのです。
しかしID/NAMESに限定した内容となりました。何故かというと…

$ docker ps -a
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                      PORTS                            NAMES
DG0da5099539        bashcms2:test         "apachectl -D FOREGR…"   2 weeks ago         Exited (255) 11 days ago    8070/tcp, 0.0.0.0:8070->80/tcp   bashcms2
DGd65fa33a85        c391afa02765          "/bin/sh -c 'target=…"   2 weeks ago         Exited (127) 2 weeks ago                                     busy_beaver
DG1de459c644        ubuntu                "/bin/bash"              2 months ago        Exited (130) 2 months ago                                    hungry_bhaskara
DG99f3eb9712        growi_elasticsearch   "/usr/local/bin/dock…"   6 months ago        Up 11 days                  9200/tcp, 9300/tcp               growi_elasticsearch_1

# コマンド列(3列目)を取ろうとすると途中で欠けてしまう
$ docker ps -a | awk '{print $3}'
IMAGE
"apachectl
"/bin/sh
"/bin/bash"
"/usr/local/bin/dock…"

# 期待している出力はコレ
COMMAND
"apachectl -D FOREGR…"
"/bin/sh -c 'target=…"
"/bin/bash"
"/usr/local/bin/dock…"

特に指定しない限りawkでは空白シーケンス(1つ以上のスペース、タブ、改行)で列を区切るため、""で括られていても途中で切れてしまうのです。

同じことがコンテナID/コンテナ名の抽出時に起きないのかというと、
コンテナID/コンテナ名に空白シーケンス(1つ以上のスペース、タブ、改行)が含まれていないことが保証されているのでちゃんと取れるようになっています。

https://github.com/moby/moby/blob/be97c66708c24727836a22247319ff2943d91a03/daemon/names/names.go
https://github.com/moby/moby/blob/1fd7e4c28d3a4a21c3540f03a045f96a4190b527/pkg/namesgenerator/names-generator.go
https://docs.docker.com/engine/reference/commandline/create/

コンテナID一覧の取得で示しているコマンドも実は1行目のラベルが不正確で、
厳密にはCONTAINER IDと出るべきです。
これはラベル行に出てくる空白を置換すれば正しく表示出来ます。

# コンテナID一覧を取得
$ docker ps -a | awk '$0=$1'
CONTAINER
DG0da5099539
DGd65fa33a85
DG1de459c644
DG99f3eb9712

# ラベル行のみsedで前処理を行う(半角スペースをアンダーバーに置換)
$ docker ps -a | sed "1s/ /_/" | awk '$0=$1'
CONTAINER_ID
DG0da5099539
DGd65fa33a85
DG1de459c644
DG99f3eb9712

最後に

執筆中に「これぐらいの処理、dockerのオプションで提供されているのでは…?」と思い
docker psのオプションを改めて眺めたら、--formatオプションにしっかりと書いてありました。
https://docs.docker.com/engine/reference/commandline/ps/#options

# コマンド列(3列目)の内容を抜き取る
$ docker ps -a --format "table {{.Command}}"
COMMAND
"apachectl -D FOREGR…"
"/bin/sh -c 'target=…"
"/bin/bash"
"/usr/local/bin/dock…"

# コンテナID、コンテナ名の取得ももちろん可能
$ docker ps -a --format "table {{.ID}}"
CONTAINER ID  
DG0da5099539  
DGd65fa33a85   
DG1de459c644  
DG99f3eb9712   

$ docker ps -a --format "table {{.Names}}"
NAMES
bashcms2
busy_beaver
hungry_bhaskara
growi_elasticsearch_1

私が調べた限りでは、コンテナID/コンテナ名の一覧を取得するにはdocker psの--formatオプションを使うか、docker psの結果をawk・grepで加工するのが簡単な方法だと思います。

それぞれ特徴を表で整理しましたので、各々自分が好きな手段を使ってみて下さい!

手段 覚えられないもの 打ちたくないもの awk,grep
docker ps --format awkの記法 シングルクォーテーション 入ってない
awk,grep docker ps --formatの記法 中括弧 入っている

Discussion