rm all files under a directory using python subprocess.call
Asked Answered
C

2

5

I'm writing a script that will move files into a .trash directory in a user's home folder. I want to add the ability to empty the trash directory by calling rm -rf /home/user/.trash/* using python's subprocess.call()

~$ touch file1
~$ trash file1
['mv', 'file1', '/home/rodney/.trash/']
~$ ls .trash
file1
~$ trash --empty
['rm', '-rf', '/home/rodney/.trash/*']
~$ ls .trash
file1

As you can see the rm command did not remove the contents of the trash. However if I execute the command directly on the command line it works.

~$ rm -rf /home/rodney/.trash/*
~$ ls .trash
~$ 

The output is from the following code

print(cmd)
subprocess.call(cmd)

What is weird about this is if I exclude the * from the last argument in the cmd list then the subprocess call works but also removes the entire .trash directory. I do not want to delete the .trash directory; only everything under it.

To sum up the question

This works

import subprocess
subprocess.call(['rm', '-rf', '/home/rodney/.trash/'])

This does not

import subprocess
subprocess.call(['rm', '-rf', '/home/rodney/.trash/*'])

Why?

Cleres answered 13/8, 2015 at 0:58 Comment(4)
Why are you shelling out to rm to do something as simple as deleting files in python?Marquesan
I thought it would be quicker to write rather than creating a file list and traversing all sub directories.Cleres
Perhaps, but it benefits you more in the long run to learn the APIs that Python has to offer. See my answer. Otherwise, you might as well just write this as a Bash script.Marquesan
Note that * does not match files that begin with a dot.Marquesan
M
9

Don't shell out.

This uses glob.glob() to identify the files to be removed, and shutil.rmtree() to remove subdirectories.

#!/usr/bin/env python
import os, os.path
import glob
import shutil

def remove_thing(path):
    if os.path.isdir(path):
        shutil.rmtree(path)
    else:
        os.remove(path)

def empty_directory(path):
    for i in glob.glob(os.path.join(path, '*')):
        remove_thing(i)

empty_directory('trash')

Example:

$ tree trash/
trash/
├── aaa
├── bbb
├── ccc
├── ddd
└── sub
    ├── iii
    └── jjj

1 directory, 6 files

$ ./go.py 

$ tree trash/
trash/

0 directories, 0 files
Marquesan answered 13/8, 2015 at 13:14 Comment(4)
use os.path.join(path, '*') instead of path + '/*'. Though os.listdir(path) might better express OP's intent (includes .* entries) than glob.Trolly
I had no idea it was this simple to do. I went ahead and implemented your suggestion. I wasn't aware that * doesn't match hidden files so I included @J.F.Sebastian's suggestion as well. For empty_directory I have [remove(os.path.join(path, f)) for f in os.listdir(path)] and everything works fine now.Cleres
Please don't use a list comprehension "just because" -- a normal for loop is Pythonic here. You're creating a list of None for every call to remove for no reason.Marquesan
While neither shelling-out to rm nor using a list comprehension will be detrimental to the performance of your code, it's a very Good Idea to be thinking about the consequences of the code you write while you're learning.Marquesan
C
4

Shell expand * into file names. You need to pass shell=True keyword argument so that shell will interpret *.

import subprocess
subprocess.call('rm -rf /home/rodney/.trash/*', shell=True)

According to subprocess - Frequently Used Arguments:

If shell is True, the specified command will be executed through the shell. This can be useful if you are using Python primarily for the enhanced control flow it offers over most system shells and still want convenient access to other shell features such as shell pipes, filename wildcards, environment variable expansion, and expansion of ~ to a user’s home directory. However, note that Python itself offers implementations of many shell-like features (in particular, glob, fnmatch, os.walk(), os.path.expandvars(), os.path.expanduser(), and shutil).

Chaunceychaunt answered 13/8, 2015 at 1:6 Comment(2)
Just tried this and now I get 'rm: missing operand' if I use a list. Joining the list managed to fix this. Thanks.Cleres
@Rodneyxr, I changed the code accordingly. Thank you for the feedback.Chaunceychaunt

© 2022 - 2024 — McMap. All rights reserved.