> For the complete documentation index, see [llms.txt](https://ansible.drx.tw/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ansible.drx.tw/18.how-to-use-loops-in-playbooks.md).

# 如何在 Playbooks 使用 loops？

## 18. 如何在 Playbooks 使用 loops？

在 Shell Script 裡，我們會使用 for 和 while 等迴圈 (loop) 來簡化重複的程式碼，而在 Ansible 我們也可用 loop 來簡化**重複的任務 (Tasks)**。以下凍仁將介紹常見的 loop 語法。

![automate\_with\_ansible\_practice-23.jpg](/files/-LpvdIBi5UI9tFgG8JQM) ▲ 圖片來源：<http://screencastsolutions.ca/looping-for-continuous-play/> 。

### 標準迴圈 (Standard Loops)

首先讓我們以簡單的**重複印出 3 筆訊息**為例。

#### Shell Script

先複習一下 Shell Script 的寫法。

1. 建立 for loop 的 Script。

   ```
   #!/bin/bash
   $ vi bash_loop.sh
   for X in 0 1 2; do
     echo Loop $X
   done
   ```

   * 在第 3 行，我們用了 `for`，並代入了 0, 1, 2 三個值到 `$X` 變數。
   * 在第 4 行時，則用了 `echo`，印出訊息和 `$X` 變數。
2. 執行 Script：可以看到底下跑了 3 次的 loop。

   ```
   $ ./bash_loop.sh
   Loop 0
   Loop 1
   Loop 2
   ```

#### Ansible Playbooks

我們需透過 `item` 和 `with_items` 來使用 Ansible 的 loop，其 `item` 為預設名，一般情況下不可修改。

> 在 Ansible 2.1 新增了 Loop Control 的語法，可透過 `loop_control` 和 `loop_var` 來自訂 item 的名字，這在多重 loop 等較複雜的環境下會有很大的幫助。

1. 建立 loop 的 playbook。

   ```
   $ vi playbook_loop.yml
   ---
   - name: a basic loop with playbook
     hosts: localhost
     tasks:
       - name: print loop message
         debug:
           msg: "Loop {{ item }}"
         with_items:
           - 0
           - 1
           - 2

   ```

   > 註：`raw` 和 `endraw` 是為了相容 GitBook 所增加的語法，您可能會在某平台上看到它，請忽略之。

   * 在第 7, 8 行裡，我們用了 `debug` module 來印出訊息，並定義 `item`。
   * 在第 9 \~ 12 行裡，則用了 `with_items` 將 0, 1, 2 的值傳入 `item`。
2. 執行 Playbook：可以看到 `print loop message` task 跑了 3 次的 loop。

   ```
   $ ansible-playbook playbook_loop.yml

   PLAY [a basic loop with playbook] *********************************************

   TASK [setup] *******************************************************************
   ok: [localhost]

   TASK [print loop message] ******************************************************
   ok: [localhost] => (item=0) => {
       "item": 0,
       "msg": "Loop 0"
   }
   ok: [localhost] => (item=1) => {
       "item": 1,
       "msg": "Loop 1"
   }
   ok: [localhost] => (item=2) => {
       "item": 2,
       "msg": "Loop 2"
   }

   PLAY RECAP *********************************************************************
   localhost                  : ok=2    changed=0    unreachable=0failed=0
   ```
3. 凍仁常用此手法來安裝多個套件，接著以建立 [`chusiang/ansible-jupyter`](https://hub.docker.com/r/chusiang/ansible-jupyter/) Docker image 的 [setup\_jupyter.yml](https://github.com/chusiang/ansible-jupyter.dockerfile/blob/master/setup_jupyter.yml) 為例。

   ```
   $ vi setup_jupyter_yml
   01 ---
   02 - hosts: localhost
   03
   04   vars:
   05     # Same package on GNU/Linux.
   06     same_packages:
   07       - bash
   08       - bash-completion
   09       - ca-certificates
   10       - curl
   11       - git
   12       - openssl
   13
   14     # Alpine Linux.
   15     apk_packages:
   16       - openssh-client
   17       - vim
   18
   19     # Debian, Ubuntu.
   20     apt_packages: "{{ apk_packages }}"
   21     ...
   22
   23   tasks:
   24     # General Linux.
   25     - name: install same packages
   26       package: name={{ item }} state=present
   27       with_items: "{{ same_packages }}"
   28       when:
   29         - same_packages is defined
   30         - ansible_pkg_mgr != "portage"
   31
   32     # Alpine Linux.
   33     - name: install apk packages
   34       apk: name={{ item }} state=present
   35       with_items: "{{ apk_packages }}"
   36       when:
   37         - apk_packages is defined
   38         - ansible_pkg_mgr == "apk"
   39
   40     # Debian, Ubuntu.
   41     - name: install apt packages
   42       apt: name={{ item }} state=present
   43       with_items: "{{ apt_packages }}"
   44       when:
   45         - apt_packages is defined
   46         - ansible_pkg_mgr == "apt"
   47     ...

   ```

   > 註：`raw` 和 `endraw` 是為了相容 GitBook 所增加的語法，您可能會在某平台上看到它，請忽略之。

   * 在第 6, 15, 20 行裡，分別宣告 `same_packages`, `apk_packages` 和 `apt_packages` 變數，並傳入了幾個套件名稱。
   * 在第 26, 27, 34, 35, 42, 43 行裡，定義了 `item`，並將 `same_packages` 變數傳入。換句話說就是 `install same packages` task 會安裝 `same_packages` 定義的所有套件。
   * 由於此例中 apk, apt 的套件名稱皆相同，故在第 20 行用了 `apt_packages: "{{ apk_packages }}"` 的手法讓 `apt_packages = apk_packages`。

### 進階迴圈 (Advanced Loops)

如有數個變數需求，可用 `item.first`, `item.second` 類似屬性的方式定義 items。

1. 建立擁有兩個 item 屬性的 loop 的 playbook。

   ```
   $ vi playbook_loop_adv1.yml
   ---
   - name: a advanced loop with playbook
     hosts: localhost
     tasks:
       - name: print loop message
         debug:
           msg: "Loop {{ item.num }}: {{ item.str }}"
         with_items:
           - { num: '0', str: 'automate' }
           - { num: '1', str: 'with' }
           - { num: '2', str: 'ansible' }

   ```

   > 註：`raw` 和 `endraw` 是為了相容 GitBook 所增加的語法，您可能會在某平台上看到它，請忽略之。
2. 執行 Playbook：這次除了跑 3 次 loop 以外，還代入 `num` 和 `str` 屬性的 items。

   ```
   $ ansible-playbook playbook_loop_adv1.yml

   PLAY [a advanced loop with playbook] *******************************************

   TASK [setup] *******************************************************************
   ok: [localhost]

   TASK [print loop message] ******************************************************
   ok: [localhost] => (item={u'num': u'0', u'str': u'automate'}) => {
       "item": {
           "num": "0",
           "str": "automate"
       },
       "msg": "Loop 0: automate"
   }
   ok: [localhost] => (item={u'num': u'1', u'str': u'with'}) => {
       "item": {
           "num": "1",
           "str": "with"
       },
       "msg": "Loop 1: with"
   }
   ok: [localhost] => (item={u'num': u'2', u'str': u'ansible'}) => {
       "item": {
           "num": "2",
           "str": "ansible"
       },
       "msg": "Loop 2: ansible"
   }

   PLAY RECAP *********************************************************************
   localhost                  : ok=2    changed=0    unreachable=0    failed=0
   ```
3. 這部份在新增多個使用者、多個軟連結 (soft link) 時都會用到。

   ```
   $ vi playbook_loop_adv2.yml
   ---
   - name: a advanced loop with playbook
     hosts: localhost
     tasks:
       - name: create multiple soft link
         file:
           src: "~/vcs/4.docs/automate-with-ansible/lab/ch18/{{ item.src }}"
           dest: "/tmp/{{ item.dest }}"
           state: link
         with_items:
           - { src: 'playbook_loop.yml', dest: 'loop0.yml' }
           - { src: 'playbook_loop_adv1.yml', dest: 'loop1.yml' }
           - { src: 'playbook_loop_adv2.yml', dest: 'loop2.yml' }

   # vim: ft=ansible :

   ```

   > 註：`raw` 和 `endraw` 是為了相容 GitBook 所增加的語法，您可能會在某平台上看到它，請忽略之。

   * 第 8 行的 `src` 的**絕對路徑**會因環境而有變動，還請特別留意一下。

### 後語

以個人經驗而言，掌握這兩個技巧就可以解決大多的迴圈需求！若想深入了解這部份，請研讀官方的 [Loops | Ansible Documentation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html) 文件 。

### 相關連結

* [Loops | Ansible Documentation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html)
* [Bash For Loop Examples | nixCraft](https://www.cyberciti.biz/faq/bash-for-loop/)

1. 在友人的提醒下補了**行號**以利閱讀，若想複製該範例，請直接上 [GitHub](https://github.com/chusiang/ansible-jupyter.dockerfile/blob/master/setup_jupyter.yml) 取用。
