Original code copyright (C) 2009-2022 Rudolf Cardinal (

This file is part of cardinal_pythonlib.

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

File operations.

class cardinal_pythonlib.fileops.FileWatcher(filenames: List[str])[source]

Watch several files for changes.

Initialize with a list of filenames to watch.

changed() → List[str][source]

Returns a list of filenames that have changed (since class instance creation or last call to this function).

cardinal_pythonlib.fileops.chmod_r(root: str, permission: int) → None[source]

Recursive chmod.

  • root – directory to walk down
  • permission – e.g. e.g. stat.S_IWUSR
cardinal_pythonlib.fileops.chown_r(path: str, user: str, group: str) → None[source]

Performs a recursive chown.

  • path – path to walk down
  • user – user name or ID
  • group – group name or ID

As per

cardinal_pythonlib.fileops.concatenate(src: List[str], dest: str, filesep: str = '\n')[source]

Concatenate multiple text files into one.

cardinal_pythonlib.fileops.copy_tree_contents(srcdir: str, destdir: str, destroy: bool = False) → None[source]

Recursive copy. Unlike copy_tree_root(), copy_tree_contents() works as follows. With the file structure:


the command

copy_tree_contents("/source/thing", "/dest")

ends up creating:

cardinal_pythonlib.fileops.copy_tree_root(src_dir: str, dest_parent: str) → None[source]

Copies a directory src_dir into the directory dest_parent. That is, with a file structure like:


the command

copy_tree_root("/source/thing", "/dest")

ends up creating

cardinal_pythonlib.fileops.copyglob(src: str, dest: str, allow_nothing: bool = False, allow_nonfiles: bool = False) → None[source]

Copies files whose filenames match the glob src” into the directory “dest”. Raises an error if no files are copied, unless allow_nothing is True.

  • src – source glob (e.g. /somewhere/*.txt)
  • dest – destination directory
  • allow_nothing – don’t raise an exception if no files are found
  • allow_nonfiles – copy things that are not files too (as judged by os.path.isfile()).

ValueError – if no files are found and allow_nothing is not set

cardinal_pythonlib.fileops.delete_files_within_dir(directory: str, filenames: List[str]) → None[source]

Delete files within directory whose filename exactly matches one of filenames.

cardinal_pythonlib.fileops.exists_locked(filepath: str) → Tuple[bool, bool][source]

Checks if a file is locked by opening it in append mode. (If no exception is thrown in that situation, then the file is not locked.)

Parameters:filepath – file to check
Returns:(exists, locked)
Return type:tuple


cardinal_pythonlib.fileops.find(pattern: str, path: str) → List[str][source]

Finds files in path whose filenames match pattern (via fnmatch.fnmatch()).

cardinal_pythonlib.fileops.find_first(pattern: str, path: str) → str[source]

Finds first file in path whose filename matches pattern (via fnmatch.fnmatch()), or raises IndexError.

cardinal_pythonlib.fileops.gen_filenames(starting_filenames: List[str], recursive: bool) → Generator[str, None, None][source]

From a starting list of files and/or directories, generates filenames of all files in the list, and (if recursive is set) all files within directories in the list.

  • starting_filenames – files and/or directories
  • recursive – walk down any directories in the starting list, recursively?

each filename

cardinal_pythonlib.fileops.get_directory_contents_size(directory: str = '.') → int[source]

Returns the total size of all files within a directory.


Parameters:directory – directory to check
Returns:size in bytes
Return type:int
cardinal_pythonlib.fileops.mkdir_p(path: str) → None[source]

Makes a directory, and any intermediate (parent) directories if required.

This is the UNIX mkdir -p DIRECTORY command; of course, we use os.makedirs() instead, for portability.

cardinal_pythonlib.fileops.moveglob(src: str, dest: str, allow_nothing: bool = False, allow_nonfiles: bool = False) → None[source]

As for copyglob(), but moves instead.

cardinal_pythonlib.fileops.preserve_cwd(func: Callable) → Callable[source]

Decorator to preserve the current working directory in calls to the decorated function.


def myfunc():

assert os.getcwd() == "/home"
cardinal_pythonlib.fileops.purge(path: str, pattern: str) → None[source]

Deletes all files in path matching pattern (via fnmatch.fnmatch()).

cardinal_pythonlib.fileops.pushd(directory: str) → None[source]

Context manager: changes directory and preserves the original on exit.


with pushd(new_directory):
    # do things
cardinal_pythonlib.fileops.relative_filename_within_dir(filename: str, directory: str) → str[source]

Starting with a (typically absolute) filename, returns the part of the filename that is relative to the directory directory. If the file is not within the directory, returns an empty string.

cardinal_pythonlib.fileops.require_executable(executable: str) → None[source]

If executable is not found by shutil.which(), raise FileNotFoundError.

cardinal_pythonlib.fileops.rmglob(pattern: str) → None[source]

Deletes all files whose filename matches the glob pattern (via glob.glob()).

cardinal_pythonlib.fileops.rmtree(directory: str) → None[source]

Deletes a directory tree.

cardinal_pythonlib.fileops.root_path() → str[source]

Returns the system root directory.

cardinal_pythonlib.fileops.shutil_rmtree_onerror(func: Callable[[str], None], path: str, exc_info: Tuple[Optional[Any], Optional[BaseException], Optional[traceback]]) → None[source]

Error handler for shutil.rmtree.

If the error is due to an access error (read only file) it attempts to add write permission and then retries.

If the error is for another reason it re-raises the error.

Usage: shutil.rmtree(path, onerror=shutil_rmtree_onerror)


cardinal_pythonlib.fileops.which_and_require(executable: str, fullpath: bool = False) → str[source]

Ensures that executable is on the path, and returns it (or its full path via shutil.which()).

cardinal_pythonlib.fileops.which_with_envpath(executable: str, env: Dict[str, str]) → str[source]

Performs a shutil.which() command using the PATH from the specified environment.

Reason: when you use run([executable, ...], env) and therefore[executable, ...], env=env), the PATH that’s searched for executable is the parent’s, not the new child’s – so you have to find the executable manually.

  • executable – executable to find
  • env – environment to fetch the PATH variable from