Обновить значение в списке словарей

У меня есть следующий список словарей:

"errorlist": [
    {
        "error": "Not found",
        "path": "/tmp/working/directory1/file1"
    },
    {
        "error": "Not found",
        "path": "/tmp/working/directory2/file1"
    },
    {
        "error": "Not found",
        "path": "/tmp/working/directory1/file2"
    },
    {
        "error": "Not found",
        "path": "/tmp/working/directory2/file2"
    }
]

Я хотел бы удалить часть значений пути «/tmp/working/», т.е. преобразовать абсолютные пути в относительные.

Я задавал подобные вопросы при поиске в Интернете, но не нашел решения. Это должно быть довольно просто, но я не могу уложить в этом голову.

Ожидаемым результатом должен быть либо исходный список с обновленными значениями, либо новый список с той же структурой.

Введение в Ansible Roles
Введение в Ansible Roles
Ansible - это отличный инструмент управления конфигурацией, который можно использовать для автоматизации настройки или развертывания на большом...
0
0
61
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ansible — не лучший инструмент для манипулирования данными. Для того, что вы хотите сделать, самым простым решением, вероятно, будет собственный фильтр, написанный на Python. Поместите следующее в filter_plugins/filters.py (где каталог filter_plugins находится в том же каталоге, что и ваша книга игр):

def replaceInAttribute(data, attr, find, replace):
    for d in data:
        if attr in d:
            d[attr] = d[attr].replace(find, replace)

    return data


class FilterModule:
    def filters(self):
        return {
            "replaceInAttribute": replaceInAttribute,
        }

И теперь вы можете написать такую ​​пьесу:

- hosts: localhost
  gather_facts: false
  vars:
    errorlist: [
        {
            "error": "Not found",
            "path": "/tmp/working/directory1/file1"
        },
        {
            "error": "Not found",
            "path": "/tmp/working/directory2/file1"
        },
        {
            "error": "Not found",
            "path": "/tmp/working/directory1/file2"
        },
        {
            "error": "Not found",
            "path": "/tmp/working/directory2/file2"
        }
    ]
  tasks:
  - debug:
      msg: "{{ errorlist | replaceInAttribute('path', '/tmp/working/', '') }}"

И получите этот вывод:

TASK [debug] *************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": [
        {
            "error": "Not found",
            "path": "directory1/file1"
        },
        {
            "error": "Not found",
            "path": "directory2/file1"
        },
        {
            "error": "Not found",
            "path": "directory1/file2"
        },
        {
            "error": "Not found",
            "path": "directory2/file2"
        }
    ]
}

Представленный здесь фильтр немного сложнее, чем должен быть, поскольку он поддерживает поиск/замену произвольных атрибутов.

Я согласен, что Ansible — не лучший инструмент для манипулирования данными, но я бы предпочел не использовать Python. В любом случае спасибо.

Jedi6 03.09.2024 20:36

@ Jedi6, но сам Ansibe и почти каждый модуль, фильтр и т. д. написаны на Python. Итак, вы все равно собираетесь использовать Python. Даже если вы обнаружите, что стоимость обслуживания и эксплуатации собственного пользовательского фильтра высока, это может оказаться дешевле, чем обслуживание и эксплуатация сборника сценариев Ansible с множеством готовых фильтров для сложных манипуляций с данными.

U880D 03.09.2024 20:44
Ответ принят как подходящий

Отфильтруйте базовые имена и создайте список словарей.

  paths: "{{ errorlist | map(attribute='path') | map('basename') |
                         map('community.general.dict_kv', 'path') }}"

дает

  paths:
    - {path: file1}
    - {path: file1}
    - {path: file2}
    - {path: file2}

заархивируйте списки и объедините элементы

  result: "{{ errorlist | zip(paths) | map('combine') }}"

дает

  result:
    - {error: Not found, path: file1}
    - {error: Not found, path: file1}
    - {error: Not found, path: file2}
    - {error: Not found, path: file2}

Подберите трубы в соответствии с вашими потребностями.


  • Example of a complete playbook for testing
- hosts: localhost

  vars:

    errorlist:
      - {error: Not found, path: /tmp/working/directory1/file1}
      - {error: Not found, path: /tmp/working/directory2/file1}
      - {error: Not found, path: /tmp/working/directory1/file2}
      - {error: Not found, path: /tmp/working/directory2/file2}

    paths: "{{ errorlist | map(attribute='path') | map('basename') |
                           map('community.general.dict_kv', 'path') }}"
    result: "{{ errorlist | zip(paths) | map('combine') }}"

  tasks:

    - debug:
        var: paths | to_yaml
    - debug:
        var: result | to_yaml
  • Fit the filters to your needs. For example, to remove any part of the string use the filter regex_replace
  paths: "{{ errorlist | map(attribute='path') |
                         map('regex_replace', '/tmp/working/(.*)', '\\1') |
                         map('community.general.dict_kv', 'path') }}"

gives

  paths:
    - {path: directory1/file1}
    - {path: directory2/file1}
    - {path: directory1/file2}
    - {path: directory2/file2}

This will result in

  result:
    - {error: Not found, path: directory1/file1}
    - {error: Not found, path: directory2/file1}
    - {error: Not found, path: directory1/file2}
    - {error: Not found, path: directory2/file2}

Вопрос: «Как мне добиться того же результата без Community.general.dict_kv?»

О: Используйте json_query. Приведенное ниже объявление дает тот же результат

  paths: "{{ errorlist | map(attribute='path') |
                         map('regex_replace', '/tmp/working/(.*)', '\\1') |
                         json_query('[].{path: @}') }}"

В Ansible 2.9 и ниже попробуйте добавить список фильтров.

  paths: "{{ errorlist | map(attribute='path') | list |
                         map('regex_replace', '/tmp/working/(.*)', '\\1') | list |
                         json_query('[].{path: @}') | list }}"

Debugging

Decompose the pipe

  path1: "{{ errorlist | map(attribute='path') }}"
  path2: "{{ errorlist | map(attribute='path') |
                         map('regex_replace', '/tmp/working/(.*)', '\\1') }}"
  path3: "{{ errorlist | map(attribute='path') |
                         map('regex_replace', '/tmp/working/(.*)', '\\1') |
                         json_query('[].{path: @}') }}"

and display the types of intermediary results

    - debug:
        msg: "{{ path1 | type_debug }}"
    - debug:
        msg: "{{ path2 | type_debug }}"
    - debug:
        msg: "{{ path3 | type_debug }}"

Мне также нужен относительный каталог каждого файла, а не только базовое имя.

Jedi6 03.09.2024 20:32

Используйте regex_replace в канале. Я добавил пример.

Vladimir Botka 03.09.2024 20:34

Тестирование в локальной среде сработало, но в реальной среде используется Ansible 2.9.x, и фильтр Community.general.dict_kv отсутствует. Как я могу достичь того же результата без него?

Jedi6 04.09.2024 13:24

Используйте json_query. Я добавил пример.

Vladimir Botka 04.09.2024 14:38

Пришлось добавить «| list» перед json_query, иначе переменная paths была пустой. Но теперь я получаю «<объект-генератор do_map в ....>» для переменной результата.

Jedi6 04.09.2024 17:22

См. Ansible: ошибка: объект генератора не повторяется.... Я добавил пример.

Vladimir Botka 04.09.2024 18:02

Другие вопросы по теме