How to set an environment variable depending of a shell command

I’m trying to run a command on a set of users. This command needs an environment variable which is different for each user and its value comes from a ‘shell command’.

An example:

- name: Run a command per user
  become: yes
  become_user: "{{ item }}"
  ansible.builtin.command: my-shell-command
  environment:
    MY_VAR: /var/run/user/$(id -u)
  loop: ['user1', 'user2']

The MY_VAR variable depends of the result of id -u.
Is it possible to set it directly and how?

Or do I need to use an intermediate step to run the id -u command for each user and register it?
In which case how to simply retrieve the specific stdout value for each user? I got a very complicated dict as a result and I’m not sure how to simply use it.

You could do it in one task if you’re willing to use ansible.builtin.shell instead of ansible.builtin.command. I’ll show that later.

First, I want to show you how you can do it with an intermediate step. This should give you enough to get started. Note that my system has users sshd and gdm, but not no_id_aaa or no_id_bbb. I’m including both so you can experiment with how to handle missing ids, both when you first look them up and when you later try to use a missing registered result.

---
# daks_01.yml
- name: Id -u for multiple users
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Get id -u for users
      ansible.builtin.shell: |
        id -u {{ item }} || echo "no_such_id"
      register: id_u_reg
      loop:
        - sshd
        - gdm
        - no_id_aaa

    - name: Dump registered data
      ansible.builtin.debug:
        msg: '{{ id_u_reg }}'

    - name: Do stuff with id_u
      ansible.builtin.shell: |
        printf "%s:: idu=%s, MY_VAR=%s\n" "{{ item }}" "{{ idu }}" "$MY_VAR"
      loop:
        - gdm
        - sshd
        - no_id_bbb
      environment:
        MY_VAR: '/var/run/user/{{ idu }}'
      vars:
        idu: '{{ [id_u_reg.results
                   | selectattr("item", "eq", item)
                   | map(attribute="stdout"), "missing"
                 ] | flatten
                   | first }}'

The output from the last task looks like this:

TASK [Do stuff with id_u] **************************************************************************************
changed: [localhost] => (item=gdm) => changed=true 
  ansible_loop_var: item
  cmd: |-
    printf "%s:: idu=%s, MY_VAR=%s\n" "gdm" "42" "$MY_VAR"
  delta: '0:00:00.002974'
  end: '2024-10-29 20:31:34.941036'
  item: gdm
  msg: ''
  rc: 0
  start: '2024-10-29 20:31:34.938062'
  stderr: ''
  stderr_lines: <omitted>
  stdout: 'gdm:: idu=42, MY_VAR=/var/run/user/42'
  stdout_lines: <omitted>
changed: [localhost] => (item=sshd) => changed=true 
  ansible_loop_var: item
  cmd: |-
    printf "%s:: idu=%s, MY_VAR=%s\n" "sshd" "74" "$MY_VAR"
  delta: '0:00:00.002834'
  end: '2024-10-29 20:31:35.119171'
  item: sshd
  msg: ''
  rc: 0
  start: '2024-10-29 20:31:35.116337'
  stderr: ''
  stderr_lines: <omitted>
  stdout: 'sshd:: idu=74, MY_VAR=/var/run/user/74'
  stdout_lines: <omitted>
changed: [localhost] => (item=no_id_bbb) => changed=true 
  ansible_loop_var: item
  cmd: |-
    printf "%s:: idu=%s, MY_VAR=%s\n" "no_id_bbb" "missing" "$MY_VAR"
  delta: '0:00:00.002766'
  end: '2024-10-29 20:31:35.295585'
  item: no_id_bbb
  msg: ''
  rc: 0
  start: '2024-10-29 20:31:35.292819'
  stderr: ''
  stderr_lines: <omitted>
  stdout: 'no_id_bbb:: idu=missing, MY_VAR=/var/run/user/missing'
  stdout_lines: <omitted>

To do it in one task…

    - name: Do it in one task
      ansible.builtin.shell: |
        id="{{ item }}"
        idu="$( id -u "{{ item }}" 2>/dev/null )"
        if [ -n "$idu" ] ; then
          export MY_VAR="/var/run/user/${idu}"
          echo $MY_VAR  # my_shell_command
        fi
      loop:
        - gdm
        - sshd
        - no_id_ccc

which outputs this:

TASK [Do it in one task] ***************************************************************************************
changed: [localhost] => (item=gdm) => changed=true 
  ansible_loop_var: item
  cmd: |-
    id="gdm"
    idu="$( id -u "gdm" 2>/dev/null )"
    if [ -n "$idu" ] ; then
      export MY_VAR="/var/run/user/${idu}"
      echo $MY_VAR  # my_shell_command
    fi
  delta: '0:00:00.004189'
  end: '2024-10-29 20:26:11.061616'
  item: gdm
  msg: ''
  rc: 0
  start: '2024-10-29 20:26:11.057427'
  stderr: ''
  stderr_lines: <omitted>
  stdout: /var/run/user/42
  stdout_lines: <omitted>
changed: [localhost] => (item=sshd) => changed=true 
  ansible_loop_var: item
  cmd: |-
    id="sshd"
    idu="$( id -u "sshd" 2>/dev/null )"
    if [ -n "$idu" ] ; then
      export MY_VAR="/var/run/user/${idu}"
      echo $MY_VAR  # my_shell_command
    fi
  delta: '0:00:00.004255'
  end: '2024-10-29 20:26:11.246869'
  item: sshd
  msg: ''
  rc: 0
  start: '2024-10-29 20:26:11.242614'
  stderr: ''
  stderr_lines: <omitted>
  stdout: /var/run/user/74
  stdout_lines: <omitted>
changed: [localhost] => (item=no_id_ccc) => changed=true 
  ansible_loop_var: item
  cmd: |-
    id="no_id_ccc"
    idu="$( id -u "no_id_ccc" 2>/dev/null )"
    if [ -n "$idu" ] ; then
      export MY_VAR="/var/run/user/${idu}"
      echo $MY_VAR  # my_shell_command
    fi
  delta: '0:00:00.005363'
  end: '2024-10-29 20:26:11.421682'
  item: no_id_ccc
  msg: ''
  rc: 0
  start: '2024-10-29 20:26:11.416319'
  stderr: ''
  stderr_lines: <omitted>
  stdout: ''
  stdout_lines: <omitted>
2 Likes

Thank you very much for this detailed answer with the two possibilities.
I’m still not sure the one I’ll use, the short more cryptic (and a little bit less secure, because it uses shell instead of command) one or the longer clearer one.

I’ll first try both to better understand how they works :slight_smile: