Ansible: How to delete files and folders inside a directory?
Asked Answered
C

25

226

The below code only deletes the first file it gets inside the web dir. I want to remove all the files and folders inside the web directory and retain the web directory. How can I do that?

- name: remove web dir contents
    file: path='/home/mydata/web/{{ item }}' state=absent
    with_fileglob:
      - /home/mydata/web/*

Note: I've tried rm -rf using command and shell, but they don't work. Perhaps I am using them wrongly.

Any help in the right direction will be appreciated.

I am using ansible 2.1.0.0

Chandlery answered 5/7, 2016 at 10:12 Comment(0)
S
196
- name: Delete content & directory
  ansible.builtin.file:
    state: absent
    path: /home/mydata/web/

Note: this will delete the directory too.

Subtangent answered 5/7, 2016 at 10:49 Comment(10)
@Chandlery You should use YAML-style arguments intead of Ansible's hacky key=value format. Like this: file: {state: absent, path: "{{ artifact_path }}"}. (You can put the arguments on multiple lines, but I can't show that in a comment.)Bubb
The OP (and myself) want a solution that will delete the contents of the folder BUT NOT the folder itself. This solution deletes the contents AND the folder itself.Aylsworth
How did this get an upvote? This deletes the directory too.Subvention
I can't up-vote the solution for the reasons above, but I wasn't looking to preserve the directory for my purposes, and having difficulty understanding what I was missing -- this was the perfect solution for me.Hedvah
Has anyone had the above code fail on them with artifact_path being null? This feels like it could be susceptible to one of those great rm -rf / moments in historyIncongruous
@Incongruous I'd do something like this just to be safe: when: artifact_path is defined and artifact_path != ""Bugloss
Not idempotent. That's Rule 1 of any configuration management task.Impenetrable
This will delete the directory too, thus not a solution to Empty directory.Onia
This answer misses the point. For example, if there is a file-system mounted under eg /home/mydata/web and you want to remove everything in the filesystem, this won't work.Trill
this solution is the simplest one here. If you want to preserve the directory, it's easy to just re-create it after the removal: ``` name: re-create directory file: state: directory path: /home/mydata/web/ ```Pecksniffian
C
102

Remove the directory (basically a copy of https://mcmap.net/q/117870/-ansible-how-to-delete-files-and-folders-inside-a-directory), Ansible does this operation with rmtree under the hood.

- name: remove files and directories
  ansible.builtin.file:
    state: "{{ item }}"
    path: "/srv/deleteme/"
    owner: 1000  # set your owner, group, and mode accordingly
    group: 1000
    mode: '0777'
  with_items:
    - absent
    - directory

If you don't have the luxury of removing the whole directory and recreating it, you can scan it for files, (and directories), and delete them one by one. Which will take a while. You probably want to make sure you have [ssh_connection]\npipelining = True in your ansible.cfg on.

- block:
  - name: 'collect files'
    find:
      paths: "/srv/deleteme/"
      hidden: True
      recurse: True
      # file_type: any  # Added in ansible 2.3
    register: collected_files

  - name: 'collect directories'
    find:
      paths: "/srv/deleteme/"
      hidden: True
      recurse: True
      file_type: directory
    register: collected_directories

  - name: remove collected files and directories
    file:
      path: "{{ item.path }}"
      state: absent
    with_items: >
      {{
        collected_files.files
        + collected_directories.files
      }}
Cedillo answered 24/1, 2017 at 19:20 Comment(1)
The first task is the most elegant solution I've seen. Note the comments elsewhere that there's a long-running feature request to add state=emptyBiform
B
96

Using shell module (idempotent too):

- shell: /bin/rm -rf /home/mydata/web/*

If there are dot/hidden files:

- shell: /bin/rm -rf /home/mydata/web/* /home/mydata/web/.*

Cleanest solution if you don't care about creation date and owner/permissions:

- file: path=/home/mydata/web state=absent
- file: path=/home/mydata/web state=directory
Bracteate answered 5/7, 2016 at 10:54 Comment(6)
Works but didn't remove files starting with . like .htaccessChandlery
Note that this might change your permissions/owner unless you set the explicitly during creation moment.Newmark
Using shell you'll get a warning of using the file module with state=absent instead of shell rm. To avoid it just add args: warn: falseLiteracy
This is actually the fastest and most readable answer in this post, even if it's not "native Ansible". You can use rm -rf {{ path }}/.[!.]* {{ path }}/* if you want to get really fancy and delete files with leading dots too.Syndicate
@Syndicate rm -rf [variable]/* is a risky operation. I do not recommend it. What if [variable] is null. That becomes rm -rf /*. Although it is prevented for many servers, it could cause a disaster.Plantaineater
This will produce an error if it's already empty and can use enough memory to hit things like the argument size limits if you have many files in a directory. This is both faster and safer since it avoids quoting issues, following symlinks unintentionally, etc. find /path/to/purge -xdev -deleteTortfeasor
I
57

I really didn't like the rm solution, also ansible gives you warnings about using rm. So here is how to do it without the need of rm and without ansible warnings.

- hosts: all
  tasks:
  - name: Ansible delete file glob
    find:
      paths: /etc/Ansible
      patterns: "*.txt"
    register: files_to_delete

  - name: Ansible remove file glob
    file:
      path: "{{ item.path }}"
      state: absent
    with_items: "{{ files_to_delete.files }}"

source: http://www.mydailytutorials.com/ansible-delete-multiple-files-directories-ansible/

Isaacisaacs answered 31/7, 2018 at 6:51 Comment(4)
yet the best solution for the question !!Twana
That will delete folder item.path as wellAffusion
At least in the present version it will not delete item.path folder.Clause
Thanks! For cleaning out a directory when some items inside it might be directories, or start with a ., I needed to add file_type: any and hidden: true to the find:. excludes was also handy.Garish
G
18

try the below command, it should work

- shell: ls -1 /some/dir
  register: contents

- file: path=/some/dir/{{ item }} state=absent
  with_items: {{ contents.stdout_lines }}
Goodson answered 5/7, 2016 at 12:4 Comment(2)
you missed to correctly escape last line with {{, }} to obtain with_items: "{{ contents.stdout_lines }}"Disentwine
It’s dangerous to use ls’ output to get filenames because it doesn’t correctly print filenames with special characters such as newlines.Dematerialize
R
17

That's what I come up with:

- name: Get directory listing
  find:
    path: "{{ directory }}" 
    file_type: any
    hidden: yes
  register: directory_content_result

- name: Remove directory content
  file:
    path: "{{ item.path }}" 
    state: absent
  with_items: "{{ directory_content_result.files }}" 
  loop_control:
    label: "{{ item.path }}" 

First, we're getting directory listing with find, setting

  • file_type to any, so we wouldn't miss nested directories and links
  • hidden to yes, so we don't skip hidden files
  • also, do not set recurse to yes, since it is not only unnecessary, but may increase execution time.

Then, we go through that list with file module. It's output is a bit verbose, so loop_control.label will help us with limiting output (found this advice here).


But I found previous solution to be somewhat slow, since it iterates through the content, so I went with:

- name: Get directory stats
  stat:
    path: "{{ directory }}"
  register: directory_stat

- name: Delete directory
  file:
    path: "{{ directory }}"
    state: absent

- name: Create directory
  file:
    path: "{{ directory }}"
    state: directory
    owner: "{{ directory_stat.stat.pw_name }}"
    group: "{{ directory_stat.stat.gr_name }}"
    mode: "{{ directory_stat.stat.mode }}"
  • get directory properties with the stat
  • delete directory
  • recreate directory with the same properties.

That was enough for me, but you can add attributes as well, if you want.

Rimma answered 22/9, 2019 at 17:18 Comment(0)
F
6

Using file glob also it will work. There is some syntax error in the code you posted. I have modified and tested this should work.

- name: remove web dir contents
  file:
    path: "{{ item }}"
    state: absent
  with_fileglob:
    - "/home/mydata/web/*"
Flanna answered 6/7, 2016 at 9:14 Comment(3)
with_fileglob works only on local machine, not remote one; isn't it true?Thereabout
True.. with_fileglob is for local onlyFlanna
Also, this doesn't deletes directories, just files are deleted.Gaiter
P
6

Following up on the most upvoted answer here (which I cannot edit since "edit queue is full"):

- name: Delete content & directory
  file:
    state: absent
    path: /home/mydata/web/

- name: Re-create the directory
  file:
    state: directory
    path: /home/mydata/web/
Pecksniffian answered 22/4, 2022 at 10:29 Comment(2)
Note that you might need to add owner and/or group and/or mode to re-create the directory with correct ownership and permissions.Krissy
true, ownership, acls,selinux labels, etc., need to be updated as well if neededPecksniffian
M
5

While Ansible is still debating to implement state = empty https://github.com/ansible/ansible-modules-core/issues/902

my_folder: "/home/mydata/web/"
empty_path: "/tmp/empty"


- name: "Create empty folder for wiping."
  file:
    path: "{{ empty_path }}" 
    state: directory

- name: "Wipe clean {{ my_folder }} with empty folder hack."
  synchronize:
    mode: push

    #note the backslash here
    src: "{{ empty_path }}/" 

    dest: "{{ nl_code_path }}"
    recursive: yes
    delete: yes
  delegate_to: "{{ inventory_hostname }}"

Note though, with synchronize you should be able to sync your files (with delete) properly anyway.

Moleskins answered 10/10, 2017 at 11:47 Comment(0)
E
5

Created an overall rehauled and fail-safe implementation from all comments and suggestions:

# collect stats about the dir
- name: check directory exists
  stat:
    path: '{{ directory_path }}'
  register: dir_to_delete

# delete directory if condition is true
- name: purge {{directory_path}}
  file:
    state: absent
    path: '{{ directory_path  }}'
  when: dir_to_delete.stat.exists and dir_to_delete.stat.isdir

# create directory if deleted (or if it didn't exist at all)
- name: create directory again
  file:
    state: directory
    path: '{{ directory_path }}'
  when: dir_to_delete is defined or dir_to_delete.stat.exist == False
Effusion answered 28/6, 2018 at 8:4 Comment(5)
I think you're missing the register for plugin_dir_deleted, right?Isiahisiahi
thanks for pointing this out. I had this piece of code taken out of one of my playbooks and made it more generic in terms of naming but forget to replace two variable namesEffusion
Good stuff, but I think the state in the last task needs to be set to "directory" rather than "present".Habitable
You should use your stat result to preserve permissions: owner from pw_name, group from gr_name, and mode from mode.Marxism
tried owner: dir_to_delete.stat.pw_name, group: dir_to_delete.stat.gr_name mode: dir_to_delete.stat.mode but it fails on me with my current Ansible version :(Effusion
J
3

Below code worked for me :

- name: Get directory listing
  become: yes
  find:
    paths: /applications/cache
    patterns: '*'
    hidden: yes
  register: directory_content_result

- name: Remove directory content
  become: yes
  file:
    path: "{{ item.path }}"
    state: absent
  with_items: "{{ directory_content_result.files }}"
Jelle answered 1/2, 2021 at 13:35 Comment(2)
From Review: Care to explain how the code works?Cropdusting
not cleaned subfolders for meRafferty
D
2

There is an issue open with respect to this.

For now, the solution works for me: create a empty folder locally and synchronize it with the remote one.

Here is a sample playbook:

- name: "Empty directory"
  hosts: *
  tasks:
    - name: "Create an empty directory (locally)"
      local_action:
        module: file
        state: directory
        path: "/tmp/empty"

    - name: Empty remote directory
      synchronize:
        src: /tmp/empty/
        dest: /home/mydata/web/
        delete: yes
        recursive: yes
Dynameter answered 14/11, 2017 at 5:33 Comment(0)
H
2

I like the following solution:

- name: remove web dir contents
  command:
    cmd: "find . -path '*/*' -delete -print"
    chdir: "/home/mydata/web/"
  register: web_files_list
  changed_when: web_files_list.stdout | length > 0

because it is:

  • simple
  • idempotent
  • fast
Hatshepsut answered 12/1, 2023 at 12:32 Comment(0)
L
1

I want to make sure that the find command only deletes everything inside the directory and leave the directory intact because in my case the directory is a filesystem. The system will generate an error when trying to delete a filesystem but that is not a nice option. Iam using the shell option because that is the only working option I found so far for this question.

What I did:

Edit the hosts file to put in some variables:

[all:vars]
COGNOS_HOME=/tmp/cognos
find=/bin/find

And create a playbook:

- hosts: all
  tasks:
  - name: Ansible remove files
    shell: "{{ find }} {{ COGNOS_HOME }} -xdev -mindepth 1 -delete"

This will delete all files and directories in the COGNOS_HOME variable directory/filesystem. The "-mindepth 1" option makes sure that the current directory will not be touched.

Landers answered 9/10, 2018 at 12:11 Comment(0)
L
1

I have written an custom ansible module to cleanup files based on multiple filters like age, timestamp, glob patterns, etc.

It is also compatible with ansible older versions. It can be found here.

Here is an example:

- cleanup_files:
  path_pattern: /tmp/*.log
  state: absent
  excludes:
    - foo*
    - bar*
Labionasal answered 8/5, 2019 at 22:35 Comment(0)
T
1

If you're looking for a method that's (a) idempotent, (b) efficient and (c) which correctly reports changes only when things have changed, try this. Not suitable for all situations since it depends on rsync:

# Idempotent method of emptying a directory
- name: Empty directories
  ansible.builtin.shell: |
    # Match dotfiles
    shopt -s dotglob

    # Make a temporary (empty) directory
    tmpdir=$( mktemp -d )

    # Rsync the temporary directory to the destination
    rsync -a --delete $tmpdir /dir/to/empty

    # Remove the temporary directory
    rm -rf $tmpdir
  register: empty_task
  changed_when: empty_task.stdout | length > 0

Explanation:

  • shopt -s dotglob ensure when using rsync that we also remove hidden files (those with a . prefix)
  • tmpdir=$( mktemp -d ) gives us an empty directory for rsync to use
  • rsync -a --delete $tmpdir /dir/to/empty nukes the target directory by making it match the new empty directory

We can assume nothing has changed if there's no output from the shell.

This is quite similar to the answer from @anatolii-pedchenko, which I really like. I just wanted a version using rsync, due to its reputation for efficiency with large directory structures.

Note that neither of our answers can be looped as-is. You'd need to handle multiple stdouts (from the .results dict) with a loop.

I would argue that this is a safe use case for the shell module, provided you correctly specify the target directory. A bit long-winded though!

Tenth answered 27/10, 2023 at 14:23 Comment(0)
S
0

Assuming you are always in Linux, try the find cmd.

- name: Clean everything inside {{ item }}
  shell: test -d {{ item }} && find {{ item }} -path '{{ item }}/*' -prune -exec rm -rf {} \;
  with_items: [/home/mydata/web]

This should wipe out files/folders/hidden under /home/mydata/web

Scuba answered 25/1, 2017 at 2:12 Comment(1)
It may work but using the shell module should always be a last resort.Dupont
A
0

Just a small cleaner copy & paste template of ThorSummoners answer, if you are using Ansible >= 2.3 (distinction between files and dirs not necessary anymore.)

- name: Collect all fs items inside dir
  find:
    path: "{{ target_directory_path }}"
    hidden: true
    file_type: any
  changed_when: false
  register: collected_fsitems
- name: Remove all fs items inside dir
  file:
    path: "{{ item.path }}"
    state: absent
  with_items: "{{ collected_fsitems.files }}"
  when: collected_fsitems.matched|int != 0
Atthia answered 22/5, 2019 at 8:30 Comment(0)
O
0

Isn't it that simple ... tested working ..

eg.

---
- hosts: localhost
  vars:
     cleandir: /var/lib/cloud/
  tasks:
   - shell: ls -a -I '.' -I '..' {{ cleandir }}
     register: ls2del
     ignore_errors: yes
   - name: Cleanup {{ cleandir }}
     file:
       path: "{{ cleandir }}{{ item }}"
       state: absent
     with_items: "{{ ls2del.stdout_lines }}"
Okie answered 13/12, 2020 at 11:56 Comment(3)
Doesn't work for me. Shows Green output for the task meaning nothing is changed and of course my files are still there in that folder. Can you show me your output ?Andrewandrewes
yup thats what I thought ... so I ended up using the ansible "find" module to first find all files (with path) to delete and registered it to a var ( in your case you used ls2del ). So now the last line in your cleanup task would change to loop: "{{ ls2del['files'] }}" and the path would be: "{{ item['path'] }}"Andrewandrewes
yes, beauty of ansible is we can have many ways of solution. In find module to include hidden files as well .. use "hidden: yes" .Okie
K
0
- name: Files to delete search
  find:
    paths: /home/mydata/web/
    file_type: any
  register: files_to_delete

- name: Deleting files to delete
  file:
    path: '{{ item.path }}'
    state: absent
  with_items: "{{ files_to_delete.files }}"
Krumm answered 18/7, 2022 at 9:48 Comment(0)
B
0

This solution worked for me and its a rather simple one, just had to read the man page of find command in linux.

- name: Remove contents of a directory
  hosts: localhost
  tasks:
    - name: Find files and directories within the directory
      shell:
        cmd: "find path_to_your_directory -mindepth 1 -delete"

ref to the man page of find:

-mindepth levels Do not apply any tests or actions at levels less than levels (a non- negative integer). Using -mindepth 1 means process all files except the starting-points.

Barsac answered 31/5, 2023 at 23:24 Comment(0)
F
0

I follow this file module its worst to try something from the official docs: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/file_module.html

Fab answered 21/6, 2023 at 10:39 Comment(0)
S
0

This is how I usually do it

- name: clean up the /home/mydata/web/ directory - eventuallly delete files older than "{{ retention }}"
  block:

    # this one detects all files inside /home/mydata/web/ and stores them in old_files variable
    # remove the comment from age to reduce the search to files having last modify date older than the indicated retention (for age syntax see https://docs.ansible.com/ansible/latest/collections/ansible/builtin/find_module.html#examples )
    - name: Select files - eventually older than "{{ retention }}"
      find:
        paths: "/home/mydata/web/"
        # age: "{{ retention }}"
        recurse: yes
      register: old_files

    # uncomment this to see python printing the detected files which will be deleted
    # - name: debug using python - show old_files 
    #   command: /usr/bin/python
    #   args:
    #     stdin: |
    #       # coding=utf-8

    #       print(" ")

    #       print("""
    #       old_files:
    #       {{ old_files }}

    #       old_files paths:
    #       {{ old_files.files | map(attribute='path') | list }}
    #       """)

    #       print(" ")


    - name: Delete the detected files
      file:
        path: "{{ item }}"
        state: absent
      with_items: "{{ old_files.files | map(attribute='path') | list }}"  # make a list of the paths of the detected files
Scandalize answered 17/1 at 16:35 Comment(0)
K
0

Idempotent solution with correct changed-when in a single task:

- shell
    cmd: rm -rvf path_to_dir/*
    register: empty
    changed_when: empty.stdout_lines | length > 0

Note this won't delete dotfiles

Kaleighkalends answered 7/2 at 10:8 Comment(0)
S
-2
  - name: delete old data and clean cache
    file:
      path: "{{ item[0] }}" 
      state: "{{ item[1] }}"
    with_nested:
      - [ "/data/server/{{ app_name }}/webapps/", "/data/server/{{ app_name }}/work/" ]
      - [ "absent", "directory" ]
    ignore_errors: yes
Sutler answered 14/8, 2018 at 15:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.