235

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

0

25 Answers 25

202
- name: Delete content & directory ansible.builtin.file: state: absent path: /home/mydata/web/ 

Note: this will delete the directory too.

Sign up to request clarification or add additional context in comments.

10 Comments

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.
How did this get an upvote? This deletes the directory too.
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 history
@ted-k42 I'd do something like this just to be safe: when: artifact_path is defined and artifact_path != ""
Not idempotent. That's Rule 1 of any configuration management task.
|
104

Remove the directory (basically a copy of https://stackoverflow.com/a/38201611/1695680), 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' ansible.builtin.find: paths: "/srv/deleteme/" hidden: True recurse: True # file_type: any # Added in ansible 2.3 register: collected_files - name: 'collect directories' ansible.builtin.find: paths: "/srv/deleteme/" hidden: True recurse: True file_type: directory register: collected_directories - name: remove collected files and directories ansible.builtin.file: path: "{{ item.path }}" state: absent with_items: > {{ collected_files.files + collected_directories.files }} 

1 Comment

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=empty
97

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 

6 Comments

Works but didn't remove files starting with . like .htaccess
Note that this might change your permissions/owner unless you set the explicitly during creation moment.
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: false
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.
@Ocab19 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.
|
59

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/

3 Comments

That will delete folder item.path as well
At least in the present version it will not delete item.path folder.
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.
20

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.

1 Comment

upvote for getting dir stats and recreating ownership etc. using those stats.
16

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 }} 

2 Comments

you missed to correctly escape last line with {{, }} to obtain with_items: "{{ contents.stdout_lines }}"
It’s dangerous to use ls’ output to get filenames because it doesn’t correctly print filenames with special characters such as newlines.
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/*" 

3 Comments

with_fileglob works only on local machine, not remote one; isn't it true?
True.. with_fileglob is for local only
Also, this doesn't deletes directories, just files are deleted.
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/ 

4 Comments

Note that you might need to add owner and/or group and/or mode to re-create the directory with correct ownership and permissions.
true, ownership, acls,selinux labels, etc., need to be updated as well if needed
The question is about how to remove the contents of the directory while keeping the directory. This answer does not do that.
The question is worded rather unfortunately, rather like a variant of XY problem. Ansible and similar tools are ultimately used to transform a system to a certain state. State-wise, the question would be more along the lines of "I want my playbook to make sure that web directory exists and is empty." This solution does that rather simply. Though this answer does it even better, retaining all aspects of the directory while abstaining from any complex logic.
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.

Comments

4

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 

5 Comments

I think you're missing the register for plugin_dir_deleted, right?
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 names
Good stuff, but I think the state in the last task needs to be set to "directory" rather than "present".
You should use your stat result to preserve permissions: owner from pw_name, group from gr_name, and mode from mode.
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 :(
3

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 

1 Comment

This is a cleaner solution compared to the one found here using rsync for the same job.
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 }}" 

2 Comments

From Review: Care to explain how the code works?
not cleaned subfolders for me
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

Comments

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.

Comments

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* 

Comments

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!

Comments

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

1 Comment

It may work but using the shell module should always be a last resort.
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 

Comments

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 }}" 

3 Comments

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 ?
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'] }}"
yes, beauty of ansible is we can have many ways of solution. In find module to include hidden files as well .. use "hidden: yes" .
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 }}" 

Comments

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.

Comments

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 

Comments

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

Comments

0

This works for sure. Basically a 2 step process. Find em, then remove em. If you want to add a 3rd step, debug em

- name: delete all files inside /somefolder/anotherfolder but not anotherfolder itself find: paths: /somefolder/anotherfolder/ # any, directory, file, link or patterns *.txt, .* file_type: any register: files_to_delete # if you want to see exactly what's being removed - debug: msg: "{{ files_to_delete }}" - name: Ansible remove file glob file: path: "{{ item.path }}" state: absent with_items: "{{ files_to_delete.files }}" 

Comments

-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 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.