# Many tasks for a single loop?

**URL:** <https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878>\
**Category:** Get Help\
**Tags:** playbook\
**Created:** [October 23, 2023, 6:21pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878 "2023-10-23T18:21:56Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![bern](https://avatars.discourse-cdn.com/v4/letter/b/e79b87/32.png) [@bern](https://forum.ansible.com/u/bern)\
**Post date:** [October 23, 2023, 6:21pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/1 "2023-10-23T18:21:56Z")

</div>

I want to write a playbook which check if a file exist for all users including root and if this file exists make some changes to this file. I have a script getUsers.fact which will get all users with uid 1000 and above.

I came out with the playbook below but of course it won’t work because of the first task which check if the file exist for all users before going to the second task which will do something with the file if it exists.

How can I loop all the users and check if the file exists, modify the file and go to the next user?

```auto
---
- name: make changes to file for all users
  hosts: all
  tasks:
  - name: Check state of file .bashrc
    ansible.builtin.stat:
      path: /home/{{ item }}/.bashrc
    register: fileStat
    with_items:
      - "{{ ansible_facts.ansible_local.getUsers.user }}"
      - root

  - name: List file
    ansible.builtin.command:
      cmd: excute.changes.on.files
    with_items:
      - "{{ ansible_facts.ansible_local.getUsers.user }}"
      - root
    when: fileStat.stat.exists
...

```

By the way, I am new to Ansible and presently reading the doc but need to fix things now.

---

<div class="post-metadata">

**Author:** ![IPvSean](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/ipvsean/32/1153_2.png) [@IPvSean](https://forum.ansible.com/u/IPvSean)\
**Post date:** [October 23, 2023, 8:14pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/2 "2023-10-23T20:14:03Z")

</div>

Maybe something like:

```auto
---
- name: make changes to file for all users
  hosts: all
  tasks:
    - name: Check state of file .bashrc for each user and modify if exists
      block:
        - name: Check state of file .bashrc for {{ item }}
          ansible.builtin.stat:
            path: "{{ '/root' if item == 'root' else '/home/' + item }}/.bashrc"
          register: fileStat

        - name: Modify file for {{ item }} if it exists
          ansible.builtin.command:
            cmd: excute.changes.on.files
          when: fileStat.stat.exists
      loop: 
        - "{{ ansible_facts.ansible_local.getUsers.user }}"
        - root

```

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [October 24, 2023, 1:40am UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/3 "2023-10-24T01:40:09Z")

</div>

Hey @bern . Welcome to the Forum.

I’m trying to reproduce your issue, and I’m stuck coming up with a `/etc/ansible/facts.d/getUsers.fact` that works with your expression `{{ ansible_facts.ansible_local.getUsers.user }}`. I’m curious what your `getUsers.fact` looks like. Here’s mine:

```sh
#!/bin/bash
# /etc/ansible/facts.d/getUsers.fact
# Returns JSON of local users with id>999 and without a 'nologin' shell.
pending=''
echo -n '['
getent passwd | while IFS=: read -r user password uid gid gecos home shell; do
  if ["$uid" -gt 999] && [[! "$shell" =~ nologin]] ; then
     printf "%s{" "$pending"
     printf '"user": "%s", ' "$user"
     printf '"uid": "%s", ' "$uid"
     printf '"home": "%s", ' "$home"
     printf '"shell": "%s"}' "$shell"
     pending=$',\n'
  fi
done
echo ']'

```

Having typed all that up and reading over it, it dawned on me that maybe your `ansible_facts.ansible_local.getUsers.user` expression isn’t working for you either.

Is that the problem?

---

<div class="post-metadata">

**Author:** ![bern](https://avatars.discourse-cdn.com/v4/letter/b/e79b87/32.png) [@bern](https://forum.ansible.com/u/bern)\
**Post date:** [October 24, 2023, 12:34pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/4 "2023-10-24T12:34:33Z")

</div>

This would have been too easy. loop is not usable in block according to the documentation.

[Ansible doc about Block](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_blocks.html#grouping-tasks-with-blocks?intcmp=701f20000012ngPAAQ)

Grouping tasks with blocks  
All tasks in a block inherit directives applied at the block level. Most of what you can apply to a single task ( **with the exception of loops** ) can be applied at the block level, so blocks make it much easier to set data or directives common to the tasks.

Here the output If I try to run it:

```auto
$ ansible-playbook playbooks.d/playbook_bashrc.yaml
ERROR! 'loop' is not a valid attribute for a Block

```

But we might be closer to something.

Thanks,

---

<div class="post-metadata">

**Author:** ![bern](https://avatars.discourse-cdn.com/v4/letter/b/e79b87/32.png) [@bern](https://forum.ansible.com/u/bern)\
**Post date:** [October 24, 2023, 12:52pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/5 "2023-10-24T12:52:44Z")

</div>

Here is my very simple script to get users:

```auto
echo {

for u in `grep 10[0-9][0-9] /etc/passwd | awk -F: '{print $1}'` ; do
  echo " \"user\": \"${u}\"";
done;

echo }

```

If I go your way, I would modify your script a bit:

```auto
echo {
getent passwd | while IFS=: read -r user password uid gid gecos home shell ; do
  if (( ${uid} > 999 )) && [[! ${shell} =~ nologin]] ; then
      printf " \"user\": ${user}\n"
  fi
done
echo }

```

And actually I prefer the modified version from yours than mine. 😃 Thanks!!

My problem is really in the logic of playbook where I have to pass _item_ to more than one tasks.

---

<div class="post-metadata">

**Author:** ![IPvSean](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/ipvsean/32/1153_2.png) [@IPvSean](https://forum.ansible.com/u/IPvSean)\
**Post date:** [October 24, 2023, 1:00pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/6 "2023-10-24T13:00:28Z")

</div>

oh just loop on an include\_tasks, sorry about that.

You can put the two task in another file, then loop on that file

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [October 24, 2023, 2:04pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/7 "2023-10-24T14:04:55Z")

</div>

In fact, your first method finds my wife’s id (`tplewis`), but misses mine (`utoddl`) because my uid doesn’t start with (or contain) `10`. Behold:

```auto
[utoddl@tango ansible]$ for u in `grep 10[0-9][0-9] /etc/passwd | awk -F: '{print $1}'` ; do echo " \"user\": \"${u}\""; done;
  "user": "tplewis"
[utoddl@tango ansible]$ getent passwd tplewis utoddl
tplewis:x:1001:1001::/home/tplewis:/bin/bash
utoddl:x:12428:12428:Todd Lewis:/home/utoddl:/bin/bash

```

But besides that, the expression `ansible_facts.ansible_local.getUsers.user` is only going to work if there’s a single user. If that’s a list of users, it’s going to require more jinja.

---

<div class="post-metadata">

**Author:** ![bern](https://avatars.discourse-cdn.com/v4/letter/b/e79b87/32.png) [@bern](https://forum.ansible.com/u/bern)\
**Post date:** [October 24, 2023, 2:21pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/8 "2023-10-24T14:21:03Z")

</div>

Well that’s a good point about the uid above 10xx. But I know in my environment all uid start with 10.

For the other point, the script get all users with uid \> 999 without any problem on my side.

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [October 24, 2023, 2:24pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/9 "2023-10-24T14:24:15Z")

</div>

Okay. What output do you get for this:

```yaml
    - name: Show getUsers
      ansible.builtin.debug:
        msg: "getUsers: {{ ansible_facts.ansible_local.getUsers }}"
    - name: Show getUsers.user
      ansible.builtin.debug:
        msg: "getUsers.user: {{ ansible_facts.ansible_local.getUsers.user }}"

```

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [October 24, 2023, 3:21pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/10 "2023-10-24T15:21:11Z")

</div>

Your modified `getUsers.fact` is generating invalid json for me.

Going back to my version from [the 3rd msg in this thread](http://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/3), these expressions are working for me:

```yaml
    - name: Show getUsers
      ansible.builtin.debug:
        msg: "{{ ansible_facts.ansible_local.getUsers }}"

    - name: Show each user returned by getUsers
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop:
        - "{{ ansible_facts.ansible_local.getUsers | map(attribute='user') }}"
        
    - name: Show each user returned by getUsers plus 'root'
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop:
        - "{{ ansible_facts.ansible_local.getUsers | map(attribute='user') + ['root'] }}"

```

So to loop over “those users” plus “root”, you could use that last expression:

```yml
    loop:
      - "{{ ansible_facts.ansible_local.getUsers | map(attribute='user') + ['root'] }}"

```

to loop over - as @IPvSean suggested - an `ansible.builtin.include_task` step.

(Only, I probably wouldn’t, for reasons I’ll state in a follow-up. Trying to stick to one salient point per post is _hard_.)

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [October 24, 2023, 3:56pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/11 "2023-10-24T15:56:04Z")

</div>

> [@utoddl](#):
>
> So to loop over “those users” plus “root”, you could use that last expression:
> 
> ```auto
> loop:
> - "{{ ansible_facts.ansible_local.getUsers | map(attribute='user') + ['root'] }}"
> 
> ```
> 
> to loop over - as @IPvSean suggested - an `ansible.builtin.include_task` step.

That’s one level too deep. It needs to be written like this:

```yaml
    loop: "{{ ansible_facts.ansible_local.getUsers | map(attribute='user') + ['root'] }}"

```

Otherwise, you get one `item` which itself is a list. This way, you get one user per item.

> [@utoddl](#):
>
> I probably wouldn’t [loop over an included task file _in this case_], for reasons I’ll state in a follow-up.

There’s an aesthetic balance, a trade-off to be made, between complexity (i.e. number of pieces) and complications (i.e. how intricate individual pieces are). Adding another file to have something to loop over increases complexity with no particular gain — except that it’s the only way to get Ansible to do _the thing_, which is frankly a design flaw in Ansible so I’m glossing over that point for this argument.

“Don’t do in shell scripts what you can do in tasks” is a guideline, not a rule. Choosing when to follow it depends on lots of factors, including who’s going to run and maintain the playbook, overall comfort with and understanding of various failure modes, comfort level with shell scripting generally, etc.

For me, looking at your original ask, I’d be happier with one Ansible task that uses a shell script, something like this:

```yaml
    - name: Do a thing at/with/to these users' .bashrc files
      ansible.builtin.shell: |
        IFS=: read -r user password uid gid gecos home shell < <(getent passwd '{{ item }}')
        if [-f "$home/.bashrc"] ; then
           echo "do a thing with '$home/.bashrc'"
           # This is where your 'excute.changes.on.files' command goes
           exit $?
        else
           echo "no '$home/.bashrc' or can't access it." >&2
           exit 0
        fi
      args:
        executable: /bin/bash
      loop: "{{ ansible_facts.ansible_local.getUsers | map(attribute='user') + ['root'] }}"
```

---

<div class="post-metadata">

**Author:** ![bern](https://avatars.discourse-cdn.com/v4/letter/b/e79b87/32.png) [@bern](https://forum.ansible.com/u/bern)\
**Post date:** [October 24, 2023, 5:13pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/12 "2023-10-24T17:13:07Z")

</div>

Thanks! Now I got it.

```auto
- name: make changes to file for all users
  hosts: all
  tasks:
  - name: Block of tasks
    ansible.builtin.include_tasks: looping_ansible_facts_tasks.yaml
    loop:

```

This has been very enlightening for me. This make me get some new concepts about Ansible.

Thanks a lot

---

<div class="post-metadata">

**Author:** ![bern](https://avatars.discourse-cdn.com/v4/letter/b/e79b87/32.png) [@bern](https://forum.ansible.com/u/bern)\
**Post date:** [October 25, 2023, 5:17pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/13 "2023-10-25T17:17:12Z")

</div>

Hello Todd,  
I found a little problem with script getUsers. Here is the corrected version:

```auto
declare -x -a UARR

# Build array of users
while IFS=: read -r user password uid gid gecos home shell ; do
    if (( ${uid} > 999 )) && [[! ${shell} =~ nologin]] ; then
        UARR=( ${UARR[*]} ${user} )
    fi
done < <( getent passwd )

# Build json output
printf "{ \"users\": ["
x=0
while (( x < ${#UARR[*]} )); do
    (( ${x} == ${#UARR[*]}-1 )) && printf "\"${UARR[${x}]}\"" || printf "\"${UARR[${x}]}\", "
    (( x++ ))
done
printf " ] }\n"

```

I found it is a good idea to check the output of the script above with jq to validate if it is a proper json format. Also, we can see it is properly working with the module setup and listing the facts about a host.

Now I have to found a way to iterate a json array. My script create a json array of users.

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [October 25, 2023, 7:23pm UTC](https://forum.ansible.com/t/many-tasks-for-a-single-loop/1878/14 "2023-10-25T19:23:09Z")

</div>

I like this better. It’s shorter, doesn’t double your last entry, nor assume there is one.

```sh
printf '{ "users": ['
sep=''
while IFS=: read -r user password uid gid gecos home shell ; do
    if (( "${uid}" > 999 )) && [[! "${shell}" =~ nologin]] ; then
        printf '%s"%s"' "${sep}" "${user//\"/\\\"}"
        sep=', '
    fi
done < <( getent passwd )
printf "] }\n"

```

It also escapes double-quotes in the `$user` which is required in the generated json. Probably not going to be an issue here, but the technique is instructive. _I_ learned something anyway, so thanks for that.

Finally, get in the habit of linting any shell scripts you write with the most excellent `shellcheck` program. It can elevate a pretty good shell scripter to the next level. Likewise for `ansible-lint` and Ansible. **Nobody has enough time to skip linting.**

> [@bern](#):
>
> Now I have to [find] a way to iterate a json array. My script create[s] a json array of users.

That’s the beauty of “facts”. Your playbook doesn’t need to care what form your data arrived in when it was ingested. It’s just a list, like any other. So now you can put your tasks in a separate file — let’s call it `bash_rc_wrangler.yml` — and you can iterate over it like this:

```yml
    - name: Run the bash_rc_wrangler tasks on each local user and "root"
      ansible.builtin.include_tasks:
        file: bash_rc_wrangler.yml
      loop: "{{ ansible_facts.ansible_local.getUsers + ['root'] }}"
      loop_control:
        loop_var: wrangle_user

```

Those last two lines create the variable `wrangle_user` and sets it to each of your users’ userids. The tasks inside `bash_rc_wrangler.yml` will use that variable to know what user they are working on. The other thing this buys you is, within that task file, if you loop over some other list, you can let it set the default loop\_var `item` without wiping out your `wrangle_user` variable. Without those lines, your users’ userids would be in `item`, which is overwritten by the next loop that comes along.
