コンテナ(docker/podman, singularity)入門

# コンテナ入門

大学の共有サーバーで機械学習させるときにsingularityを使うことを推奨していたので、導入がスムーズになるように研究室向けに書きました。

コンテナの導入と使い方、 [docker](https://www.docker.com/)/[podman](https://podman.io/) のイメージ作成、[singularity](https://docs.sylabs.io/guides/latest/user-guide/index.html) のイメージ作成と docker/podman のコンテナから singularity で使える形式への変換について記載します。  
singularity のコンテナファイルをローカルでビルドするために docker/podman を使用するので、docker/podman についての解説もしています。  
なお、注釈は完全な余談です。

## singularity Quick Start

- CLI 操作のみ記述 (GUI を言葉で説明するのは難しい...)
- [windows でコンテナを使えるようにする]()  
  → [singularity 向けコンテナイメージのビルド環境の構築]()  
  → [singularity の操作]()  
  の順に読むと singularity についてはすぐに使えます

```sh
# 対話シェルの起動: --nvオプションでGPUの使用
singularity shell --containall --nv <name>.sif
# コンテナでコマンドを実行: --nvオプションでGPUの使用
singularity exec --containall --nv <name>.sif <cmd>
# ホストファイル・ディレクトリのマウント (パスは絶対パス)
singularity shell --containall -B /path/to/file/or/dir <name>.sif
# ,で繋げると複数マウント
singularity shell --containall -B /path/to/file/or/dir,/path/to/file/or/dir <name>.sif
# マウントする場所を指定する (read onlyにする場合、末尾に:roを追加)
singularity shell --containall -B /path/to/host:/path/to/container,/example:/mnt:ro <name>.sif
```

## docker/podman Quick Start

- CLI 操作のみ記述 (GUI を言葉で説明するのは難しい...)
- ローカルマシンで開発や検証をしたい人向け
- docker を使っているなら`podman`を`docker`に読み替えてください

```sh
# コンテナイメージのpull (<username> = <image_name>のときはどちらか一方だけでいい)
podman pull (<remote>/)<username>/<image_name>:<tag>
# ex) podman pull docker.io/ubuntu/ubuntu:22.04
# コンテナのバックグラウンド起動
podman run -id --name <name> <username>:<tag>
# コンテナにホストのファイルやディレクトリをマウントしてバックグラウンド起動
podman run -id -v /absolute/path/to/host:/absolute/path/to/container --name <name> <username>/<image_name>:<tag>
# コンテナでシェルの起動
podman exec -it <name> bash
# コンテナの終了
podman stop <name>
# 起動コンテナの確認
podman ps
# 作成した全てのコンテナの確認
podman ps -a
# コンテナの破棄
podman rm <name>
# コンテナイメージの破棄
podman rmi <username>/<image_name>:<tag>
```

## 用語など

- 説明にあるコマンドと実行結果の見方

```
# コメントアウト
<cmd>  # 実行コマンド ($を含めないで実行する)
> foobar # 実行結果
```

- Linux ディストリビューション  
  Linux は思想の違いで様々な形で配布されています。  
  特にパッケージ管理の仕方が大きく違います。  
  ubuntu・debian で使われている`apt`について知っておけばとりあえず大丈夫

## コンテナとは

```
A container is a standard unit of software that packages up code and
all its dependencies so the application runs quickly and
reliably from one computing environment to another.

(https://www.docker.com/resources/what-container/ より)
```

あるコードとそれを動かすための依存関係をパッケージ化して、どのマシン環境でも関係無く動かすための技術です。
現在主流のコンテナ[^1]は、基本的には 1 サービス 1 コンテナで動かします。

使う分には実験(使う機械学習モデル)ごとにホストと隔離された root が使える環境を作ってその中で作業するんだなくらいで大丈夫です。

<details>
<summary>大分余談: docker/podmanとsingularity</summary>

docker は Docker Inc が、podman は Redhat が開発しているコンテナです。  
どちらもコンテナの業界標準である OCI ([Open Container Initiative](https://ja.wikipedia.org/wiki/Open_Container_Initiative))に準拠しています。  
細かい仕様の違い[^2]はありますが、コマンドとオプションは高い互換性があります。

singularity は HPC (High Perfomance Computer)上で実行することを想定し開発されたコンテナです。  
環境をファイルに出力し、より可搬性の高いコンテナ環境を提供します。  
また、コンテナ内外で同じユーザーで実行されます。 (docker/podman は設定しなければコンテナ内では root ユーザー)  
OCI とは互換性がないため docker/podman で作成した環境を直接 singularity で使うことはできません。

</details>

## singularity のセットアップ
 
Ubuntu on WSL2にsingularityをインストールします。

### WSL2のセットアップ

- コマンドラインから
    1. 管理者権限で`powershell`もしくは`windows terminal`を開き`wsl --install`を実行
    2. 一般ユーザーで`powershell`もしくは`windows terminal`を開き直して`wsl --install Ubuntu`を実行
    3. `wsl -d Ubuntu`で起動 (2. の時点でユーザーの登録を求められたらここはスキップ)
    4. 初回セットアップを行う (ユーザーの登録とパスワードの設定)

- windows storeから
    1. windows storeを開き`wsl`で検索
    2. `Windows Subsystem for Linux`をインストール
    3. `ubuntu`で検索
    4. `Ubuntu 22.04 LTS`をインストール後、Openで起動 (ターミナルまたはpowershellが開く)
    5. 初回セットアップを行う (ユーザーの登録とパスワードの設定)
    
### singularityのインストール
1. 依存パッケージのインストール
    ```sh
    sudo apt update
    sudo apt install -y libfuse2 uidmap squashfs-tools-ng
    ```

2. singularityのインストール
    - 導入したubuntuのバージョンが22.04であることが前提
    - 異なるバージョンはjammyを適切なものに変更
    ```sh
    VERSION=4.0.1
    wget https://github.com/sylabs/singularity/releases/download/v$VERSION/singularity-ce_$VERSION-jammy_amd64.deb
    sudo dpkg -i ./singularity-ce_$VERSION-jammy_amd64.deb
    rm ./singularity-ce_$VERSION-jammy_amd64.deb
    ```

### 作業ディレクトリの作成

- 作業ディレクトリの下に`containersというディレクトリを作成
- `Dockerfile`と`recipe.def` はこの後で作成

```
workdir
└── recipe.def
```

### 定義ファイル(`recipe.def`)の作成

- pytorch と tensorflow に加え`git`と`wget`が使えるコンテナを作成する

```txt
BootStrap: docker
From: pytorch/pytorch:2.0.0-cuda11.7-cudnn8-devel

%setup

%files

%environment
    export DEBIAN_FRONTEND=noninteractive
    export LANG=ja_JP.UTF-8

%post
    apt update
    apt install -y \
        git \
        wget \
        locales
    conda install \
        pip
    pip install \
        tensorboard
```

<details>
<summary>定義ファイルの詳細</summary>

##### 必須

- Bootstrap
  - ベースイメージが存在する外部リポジトリを定義、基本的には docker 、他は公式の Docs 参照
- From
  - 使用するイメージ。docker であれば[DockerHub](https://hub.docker.com/)から探す
- %post
  - 必要なパッケージのインストールや git リポジトリのクローンなど
  - シェルスクリプトを書くのと同様に

##### 良く使う

- %setup
  - **コンテナ外**つまり**ホスト**上で行なう操作を記述
  - シェルスクリプトを書くのと同様に
  - 非推奨
- %files
  - ホストに存在するファイルをコンテナにコピーする
  - GitHub にない自作スクリプトやデータセットをコンテナで使うときに使用
  - `<source> <target>`のように記述: source=ホストのデータへのパス、target=コンテナ内のパス、Linux のディレクトリ構造と同じ
    - Ex. README.md /workspace/README.md
    - ビルドを実行したディレクトリからの相対パスで記述可能
- %environment
  - 環境変数の定義
  - `export foo=var`のように記述

#### 使わなくても困らない

- %test
  - ファイルが適切にクローンやコピーされたかなどのテストプログラムを定義
  - シェルスクリプトを書くのと同様に
- %startscript
  - ビルド時のみに実行されるスクリプトの定義
- %runscript
  - コンテナを呼び出した時に必ず実行されるスクリプトの定義
  - jupyter の起動などに
- %labels
  - メタデータの定義
  - `name value`で定義
</details>

### 環境のビルドと確認

1. 環境のビルド
    ```
    singularity build --fakeroot sample.sif recipe.def 
    ```
2. 環境の確認
    ```
    singularity run sample.sif
    ```
### docker/podmanイメージからのビルド
docker/podmanで作成したイメージをsingularityで使える形式へビルドします。  
これは、singularityとdocker/podmanが同一のインスタンス(ここではUbuntu-22.04 on WSL2)で実行されていることが前提です。

- dockerの場合

    ```sh
     singularity build --fakeroot test.sif docker-daemon://<name>(/<imagename>:<tag>)
    ```
- podmanの場合
    - rootlessで動作している場合: `--docker-host unix:///var/run/user/<user_id>/podman/podman.sock`
    - rootfullで動作している場合: `sudo`を付けて`--docker-host unix:///var/run/podman/podman.sock`

    ```sh
    singularity build --docker-host unix:///var/run/<path/to/each/user>/podman/podman.sock --fakeroot test.sif docker-daemon://<name>(/<imagename>:<tag>)
    ```

## windows でコンテナを使えるようにする

ここでは [podman-desktop](https://podman-desktop.io)[^3]を使って windows マシンでコンテナを使えるようにします。  
共用のマシンがあると思いますが、以下の点から導入することをオススメします。

- 共用マシンには root 権限が付与されていないが、自分のマシンでは root 権限を持てる。
- 手元のマシンだと環境の構築とテストがしやすい。
- 共用マシンの OS のバージョンに左右されずに作業ができる。
- Linux の勉強がしやすい。

### 導入手順

windows 10 version 2004 以上が必要です。

1. [podman-desktop のダウンロードページ](https://podman-desktop.io/)からダウンロード
    - winget を使ってるなら`winget install -e --id RedHat.Podman-Desktop`
2. podman-desktop を起動
    - WSL2の設定など必要なコンポーネントが無いときは`podman-desktop`の指示に沿って設定すればいい

1. windows-terminal を起動 (スタートメニューで`terminal`と入力すると出てくる)
    - windows のバージョンによってはインストールされていないことがあります。  
     その時は powershell を起動してください。
2. `podman -v`と入力してバージョンが出てくれば OK

## docker/podman の操作

singularityはコンテナイメージを固定するために柔軟な変更が困難です。   
そのため、docker/podmanを使って環境をテストしてからsingularityで固定すると良いでしょう。  
まだ、

### コンテナイメージ の pull

コンテナを使うには、コンテナイメージを持ってくる必要があります。
今回は ubuntu 22.04 を使用します。
別のイメージを使う場合は`ubuntu:22.04`を置き換えます。

- podman-desktop
  1. サイドパネルから Images -> Pull an Image
    ![2023-06-30_14-37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/f98578e1-c49e-2e18-e622-b39ace11fa0d.png)

  2. Image to Pull に`ubuntu/ubuntu:22.04`と入力
    ![2023-06-30_14-39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/06007b70-35e9-806d-2437-f2443376f724.png)

- CLI
  1. `podman pull ubuntu/ubuntu:22.04`と入力
  2. 選択肢が出てきたら`docker.io`を選択

#### 解説

`ubuntu:22.04`を見ると`name/image_name:tag`の形式になっています。  
name は image_name を管理している人や団体、image_name は使いたいソフトや Linux ディストリビューションの名前、tag でバージョンを指定します。  
image は[DockerHub](https://hub.docker.com/)から検索します。  
tag は指定しないことも可能です。  
その場合、最新のバージョンが pull されます。
実験環境を再現可能なものにするため tag の利用を強く勧めます。

- ubuntu を検索した例 (`docker pull ubuntu/ubuntu:<tag>`の docker を podman に置き換えると pull できる)
  ![2023-06-30_14-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/22e8a83e-2f5e-0927-d193-7df2d15b10a8.png)

### コンテナの起動

Container Name は任意の文字列です。  
指定しないと適当な文字列が割り振られます。

- podman-desktop
  1. サイドパネルから Images -> ubuntu:22.04 の再生ボタン
  2. `Container name`に`HelloWorld`と入力
  3. `Environment variables`に Name->`FOO`、Value->`BAR`と入力
  4. Start Container で起動
     ![2023-06-30_14-57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/6c262107-0d2d-9bac-91f4-a9c9fd6c3be2.png)

  5. サイドパネルから Containers -> HelloWorld -> Terminal  
    `ls`と入力して出力があれば OK
    ![2023-06-30_15-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/51cde4cd-8466-091b-8b92-5a670ba33962.png)

- CLI
  1. `podman run -id --name HelloWorld ubuntu:22.04`
  2. `podman exec -it HelloWorld bash`  
     `root@<hash>:/# `という表示が出れば OK

#### ホストのファイルやディレクトリをマウントしての起動

ホストのファイルやディレクトリは起動時にオプションで指定することでコンテナにマウントしてコンテナ内から使用できるようにします。  
 データセットはコンテナに含めることによるコンテナの肥大化を防ぐため、またモデルの学習結果をホストから利用するためにマウントを利用します。

- podman-desktop
  - コンテナ作成時の画面の`Volumes`から**絶対パス**で指定する  
    左がホスト、右がコンテナ内の絶対パス  
    ![2023-07-03_18-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/cfa30ddc-b0b5-9dd7-de66-c1c6f3169ea0.png)

- CLI
  - `-v, --volume`オプションを`podman run`に追加する ex)
    ```
    podman run -id -v /home/user/study/exp1:/tmp/exp1 --name HelloWorld ubuntu:22.04
    ```

### 解説

デスクトップからの操作はそのままなので割愛します。  
`podman run`でコンテナを起動します。  
コンテナイメージで常駐するようなアプリ(web サーバーなど)が指定されていない場合、起動したコンテナはすぐにストップします。  
これを防ぐためにオプション`-i, --interactive`で起動状態を維持します。
また、バックグラウンドで起動させるためにオプション`-d, --detach`を指定しています。

起動したコンテナ環境に入るには`podman exec`を使います。
`podman exec`ではコマンドが実行されるとコンテナから弾き出されます。
今回は bash を起動してそのままコンテナに留まりたいので、`podman run`の時と同じようにオプション`-i, --interactive`を指定して維持しています。  
また、`-t, --tty`を指定してシェルで対話できるようにしています。

その他のオプションは`podman run --help`、`podman exec --help`で出力できます。

大体は  
`podman run -id --name <name> <image>:<tag>`でコンテナを起動、  
`podman exec -it <name> bash`でコンテナ環境に入るので事足りると思います。  
また、`podman run --rm -it <image>:<tag> bash`使い捨ての環境を作成できます。(オプション`--rm`は remove)  
これはコンテナの bash から Exit した時に起動したコンテナも破棄します。

### コンテナの確認

- podman-desktop
  - 非起動状態 (status が枠のみ)
    ![2023-07-03_18-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/52149208-c1e9-8e78-3571-430a9c9d4e71.png)

  - 起動状態 (status が緑色になる)
    ![2023-07-03_18-04_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/dc76a66a-5e91-eba2-d815-a70bb16fdfa9.png)

- CUI
  - 起動コンテナの確認 →`podman ps`  
    STATUS が Up \<time\>になっている
    ![2023-07-03_18-07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/8950a325-b371-aa67-1f88-ac8e567a5c73.png)

  - 作成した全てのコンテナの確認 →`podman ps -a`  
    STATUS が Exit になっている
    ![2023-07-03_18-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/0bf6e0f4-b6b5-bb4e-0790-655c9a060b4d.png)


### コンテナの停止と破棄、コンテナイメージの破棄

実験が一通り終わり次の実験に移るときはコンテナを破棄することが良いでしょう。  
コンテナは起動するほどディスク容量を消費します。

- podman-desktop
  1. サイドパネルから Containers -> HelloWorld の停止ボタン
  2. `status`が黒くなったら、ゴミ箱ボタン
    ![2023-06-30_15-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178139/bec08c89-fd96-ffa5-fc04-f9c31457ee9b.png)
  3. サイドパネルから Images -> docker.io/ubuntu:22.04 のゴミ箱ボタン
- CLI
  1. `podman stop HelloWorld`でコンテナを停止
  2. `podman rm HelloWorld`でコンテナを破棄
  3. `podman rmi ubuntu:22.04`

## コンテナへパッケージを追加

[image の pull]()と[コンテナの起動]()を元に ubuntu:22.04 のコンテナで bash が実行されていることを前提に進めます。  
始めてコンテナに入ると`less`や`unzip`などのコマンドが無いことに気付くでしょう。(`less -h`や`unzip -h`を実行してみてください。)  
コンテナは必要最小限の環境であり、パッケージを追加することで実験環境を構築します。  
ここでは`pytorch`や`tensorflow`のベースイメージで使われる`apt`についてのみ説明します。  
なお、文献によっては`apt-get`と書かれていることがありますが、直近の ubuntu では`apt`に変更されました。

### リモートリポジトリのアップデート

```
apt update
```

### パッケージのインストール

`-y`を付けるとパッケージをインストールするかの確認がスキップされる

```
apt install -y less
# 複数パッケージを一度に追加 (末尾に\で繋ぐ)
apt install -y \
  git \
  python-is-python3
```

### キャッシュの削除

```
apt clean
```

### パッケージの検索

あいまい検索でヒットしたものが列挙される。  
あまり使わない。  
web で docs に書いてある方法や先人のやっているインストールパッケージに従った方が早い。

```
apt search libsound
> Sorting... Done
> Full Text Search... Done
> libsoundio-dev/jammy 2.0.0-2 amd64
>   cross platform audio input and output library (development files)
>
> libsoundio2/jammy 2.0.0-2 amd64
>   cross-platform audio input and output library
> ...
```

### ppa の追加

ubuntu や debian のリモートリポジトリには古くて安定しているバージョンが載せられている。  
最新バージョンを使いたい場合、そのパッケージが独自にリモートリポジトリを持っていてそこから利用できることがある。

```
# neovimの最新バージョンをインストールする
# ppa追加の準備
apt install -y software-properties-common
# ppaの追加
add-apt-repository ppa:neovim-ppa/stable
apt update
apt install -y neovim
```

### コンテナの保存
実行中のコンテナからコンテナイメージを作成します。

```sh
podman commit <container-hash or name> <name>(/<imagename>:<tag>)
```

## Dockerfile を使ったコンテナイメージの作成

ここまでで CLI から環境を作ることができます。  
しかし、コミットを忘れるとその環境は全て破棄され再現できません。  
再現可能な環境を作るためには`Dockerfile`を用いて環境を定義することが望ましいです。  
ここでは`python`と`conda`が使える環境を作りながら説明します。  
下のようなディレクトリ構造にします。

```
example
├── Dockerfile
└── requirement.txt
```

### Dockerfile の作成

適当なエディタで下のファイルを作成  
`pip`と`conda`が混在していますが、説明用ということで...。

```Dockerfile
FROM docker.io/ubuntu/ubuntu:22.04

RUN apt update && \
    apt install -y \
      wget && \
    apt clean
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \
    bash Miniconda3-latest-Linux-x86_64.sh -b -p /opt/miniconda3 && \
    rm -r Miniconda3-latest-Linux-x86_64.sh
ENV PATH /opt/miniconda3/bin:$PATH

WORKDIR /workdir
COPY requirement.txt requirement.txt
RUN conda update -n base -c defaults conda && \
   pip install -r requirement.txt && \
   conda install -c conda-forge \
     matplotlib

CMD ["/usr/bin/env", "bash"]

```

#### 解説

- `FROM`: コンテナのベースイメージを指定。  
  `podman pull`と同じように`<remote>/<username>/<image_name>:<tag>`で指定。
- `RUN`: コマンドを実行。
  `&&`で複数コマンドを繋げて列挙していく。  
  `\`で改行を解釈させる。(複数行にして人に見やすいようにしている。)
  ```
  apt update && \  # && はコマンドの実行が成功したら次のコマンドを実行するという意味
  apt install -y \ # 改行。apt install -y wgetと同義で解釈される。
      wget
  ```
- `ENV`: 環境変数を定義。  
  `ENV 変数名 値`
- `WORKDIR`: 指定したパスに移動する。存在しない場合は作成する。  
  新しく`WORKDIR`が指定されるまでそのパスの元でコマンドが実行される。
- `COPY`: ホストのファイルをコンテナにコピーする。  
  `COPY path/to/host path/to/container`
- `CMD`: `podman run`で実行されるコマンド  
  `podman run <image_name> <cmd>`で上書き可能

上記を知っていれば大体は対応できます。  
対応できないときはリファレンスの参照や都度知らべてください。(特に何をベースイメージにするかなど)

### コンテナイメージのビルド

- podman-desktop
- CLI
  1. `podman build -t example/python:22.04` .

#### 解説

`-t, --tag`でコンテナイメージに名前を付けます。  
Dockerfile が存在するディレクトリを指定して実行します。  
コンテナの起動は先に解説した通りです。

### Dockerfile 作成の Tips

コンテナイメージのサイズを小さくするために、`RUN`をできるだけ少なくすることが推奨されます。  
コードの視認性が落ちること、デバッグがしにくくなるので慣れてから意識するといいでしょう。  
例えば、先の`Dockerfile`は下のように書き直されます。

```Dockerfile
FROM docker.io/ubuntu/ubuntu:22.04

ENV PATH /opt/miniconda3/bin:$PATH
WORKDIR /workdir
COPY requirement.txt requirement.txt
RUN apt update && \
    apt install -y \
      wget && \
    apt clean && \
    wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \
    bash Miniconda3-latest-Linux-x86_64.sh -b -p /opt/miniconda3 && \
    rm -r Miniconda3-latest-Linux-x86_64.sh && \
    conda update -n base -c defaults conda && \
    pip install -r requirement.txt && \
    conda install -c conda-forge \
      matplotlib

CMD ["/usr/bin/env", "bash"]

```

## 余談： Linux の操作

コンテナは何かしらの Linux ディストリビューションなので操作は UNIX コマンドで行います。

```
pwd, env, echo, ls, find, mkdir, cp, ln, chmod, chown, grep, egrep, xargs
```

と[パイプ・リダイレクト](https://qiita.com/xyz666/items/b555be4dadd06d7a61e7)については覚えておいて損はないでしょう。

下に簡単な例と実行結果をまとめました。
[image の pull]()と[コンテナの起動]()を元に ubuntu:22.04 のコンテナで bash が実行されていることを前提に進めます。

### ディレクトリの作成

```
# mkdir -p <path/to/dir>
mkdir workdir            # workdirを作成
mkdir -p workdir/foo/bar # 親ディレクトリが無い時、親ディレクトリも作成する
```

### current dir の移動

```
# cd <dir name>
cd workdir
```

### 空ファイルの作成

```
# touch <file name>
touch foo.txt
touch .hidden.txt  # .から始めると隠れファイル(ディレクトリ)となってls では表示されない
```

### ファイル・ディレクトリの列挙

```
ls    # 列挙
ls -a # .から始まる隠れファイル(ディレクトリ)も列挙
```

### シンボリックリンクの作成

シンボリックリンクは別のパスにあるファイルやディレクトリを参照します。  
散けているファイルを容量を圧迫せずに別のディレクトリにまとめることができます。

```
ln -sf foo.txt foobar.txt
# 参照元が→で表わされている
ls -l
> lrwxrwxrwx 1 root root    7 Jul  4 05:47 foobar.txt -> foo.txt
```

### ファイル・ディレクトリの検索

```
find ./ -name "foo.txt"             # "foo.txt"を配下のディレクトリ全てから検索
find ./ -name "*.txt"               # 拡張子が ".txt"であるファイルを配下のディレクトリ全てから検索
find ./ -name "foo.txt" -maxdepth 2 # "foo.txt"を配下のディレクトリのうち深さ2までで検索
find ./ -type f                     # 配下のディレクトリにある全てのファイルを検索
find ./ -type d                     # 配下のディレクトリにある全てのディレクトリを検索
```

### パイプとリダイレクト

Linux ではパイプやリダイレクトを使うことでコマンドの実行結果を他のコマンドに与えることができます。  
複数ファイルに対して同じ操作を行なうときに便利です。

- リダイレクト

```
# >がリダイレクト (echoの結果をbar.txtに出力)
echo "Hello World !" > bar.txt
cat bar.txt # bar.txtの内容を表示
> Hello World !
# bar.txtに追記
echo "This is container tutorial." >> bar.txt
# bar.txtを新しい出力で置き換える
echo "Replace !" >| bar.txt
# EOFが再度出現するまでの文字列をsample.shに出力
# 簡単なスクリプトならエディタが無くても書ける
cat <<EOF > sample.sh
echo $FOO
FOO="Hello World"
echo $FOO
>>EOF
bash sample.sh
> BAR
> Hello World
```

- パイプ

```
# catの出力をgrepに渡している
# grep <語>で語にヒットする行を出力
cat sample.sh | grep Hello
> FOO="Hello World"
```

### 特定の文字列を含むファイルの検索

例えば、`numpy`を import しているファイルを知りたいというような状況で役立ちます。

```
egrep -r echo ./  # 配下のファイル全てに対してechoを含むファイルを検索
```

## 文字列の置換

`sed`を用います。  
[正規表現](https://www.tohoho-web.com/ex/regexp.html)を使って文字列の置換をします。  
文字列の挿入や特定の行に対しての置換もできます。

```
# s/置換前/置換後/g (置換前の語にヒットした全てに対して置換する)
cat sample.sh | sed -e 's/Hello/New/g'
echo $FOO
FOO="New World"
echo $FOO
# 置換して上書き (-i オプション)
sed -i -e 's/Hello/New/g' sample.sh
# 最終行に"echo END"を追記 ($a<追加文字列>)
sed -i '$aecho END' sample.sh
```

## 複数ファイルの操作

`xargs`で並列処理ができる。
拡張子の一括変更や複数ファイルの移動に便利

```
# ファイル名だけ表示
find ./*.txt -type f | xargs baesname
# -I% は渡された結果を%がある位置に置くオプション
# 検索でヒットしたファイルを別のディレクトリにコピーする
find ./*.txt -type f | xargs -I% cp % ./foo/bar
# 拡張子の一括変更
find ./*.txt -type f | xargs -I% sh -c 'cp % ./foo/bar/$(basename % | sed -e "s/.txt/.md/g")'
ls ./foo/bar
> foo.txt bar.txt foo.md bar.md
```

## 参考文献

- [【Dockerfile】Docker に Conda 環境を構築し、仮想環境を Activate する](https://qiita.com/kuboko-jp/items/6388c186e16028d3e699)
- [LINUX パイプとリダイレクト](https://qiita.com/xyz666/items/b555be4dadd06d7a61e7)
- [とほほの正規表現入門](https://www.tohoho-web.com/ex/regexp.html)
- [Dockerfile リファレンス](https://docs.docker.jp/engine/reference/builder.html#workdir)
- [apt コマンドチートシート](https://qiita.com/SUZUKI_Masaya/items/1fd9489e631c78e5b007)

[^1]:
    [Linux Containers (lxc)](https://linuxcontainers.org/ja/)のようにコンテナを超軽量の VM として用いる場合もあります。
    (docker のようなアプリケーションコンテナと対比してシステムコンテナと呼ばれます。)

[^2]:
    docker はデーモンが常駐(実行ユーザーに root 権限が必要)しますが、podman はデーモンレス・ルートレスコンテナとして実行できます。
    自分のマシンで使う分にはあまり問題ではありませんが...。

[^3]:
    [docker-desktop](https://www.docker.com/products/docker-desktop/)がよく使われますが、私が普段使っているのが podman なので布教も兼ねて podman-desktop を選択しました。2023/06/30 現在 v1.1 がリリースされているので使用に耐えうると判断したのも要因です。
    え？docker-compose が使いたい？podman-compose もあるし podman pod もあるよ...。