# 如何在 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) 取用。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ansible.drx.tw/18.how-to-use-loops-in-playbooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
