Deleting all files except ones mentioned in config

2019-05-05 17:23发布

问题:

Situation:

I need a bash script that deletes all files in the current folder, except all the files mentioned in a file called ".rmignore". This file may contain addresses relative to the current folder, that might also contain asterisks(*). For example:

1.php
2/1.php
1/*.php

What I've tried:

  • I tried to use GLOBIGNORE but that didn't work well.
  • I also tried to use find with grep, like follows:

    find . | grep -Fxv $(echo $(cat .rmignore) | tr ' ' "\n")

回答1:

It is considered bad practice to pipe the exit of find to another command. You can use -exec, -execdir followed by the command and '{}' as a placeholder for the file, and ';' to indicate the end of your command. You can also use '+' to pipe commands together IIRC.

In your case, you want to list all the contend of a directory, and remove files one by one.

#!/usr/bin/env bash

set -o nounset
set -o errexit
shopt -s nullglob # allows glob to expand to nothing if no match
shopt -s globstar # process recursively current directory

my:rm_all() {
    local ignore_file=".rmignore"
    local ignore_array=()
    while read -r glob; # Generate files list
    do
        ignore_array+=(${glob});
    done < "${ignore_file}"
    echo "${ignore_array[@]}"

    for file in **; # iterate over all the content of the current directory
    do
        if [ -f "${file}" ]; # file exist and is file
        then
            local do_rmfile=true;
            # Remove only if matches regex
            for ignore in "${ignore_array[@]}"; # Iterate over files to keep
            do
                [[ "${file}" == "${ignore}" ]] && do_rmfile=false; #rm ${file};
            done

            ${do_rmfile} && echo "Removing ${file}"
        fi
    done
}

my:rm_all;


回答2:

If we assume that none of the files in .rmignore contain newlines in their name, the following might suffice:

# Gather our exclusions...
mapfile -t excl < .rmignore

# Reverse the array (put data in indexes)
declare -A arr=()
for file in "${excl[@]}"; do arr[$file]=1; done

# Walk through files, deleting anything that's not in the associative array.
shopt -s globstar
for file in **; do
  [ -n "${arr[$file]}" ] && continue
  echo rm -fv "$file"
done

Note: untested. :-) Also, associative arrays were introduced with Bash 4.

An alternate method might be to populate an array with the whole file list, then remove the exclusions. This might be impractical if you're dealing with hundreds of thousands of files.

shopt -s globstar
declare -A filelist=()

# Build a list of all files...
for file in **; do filelist[$file]=1; done

# Remove files to be ignored.
while read -r file; do unset filelist[$file]; done < .rmignore

# Annd .. delete.
echo rm -v "${!filelist[@]}"

Also untested.

Warning: rm at your own risk. May contain nuts. Keep backups.

I note that neither of these solutions will handle wildcards in your .rmignore file. For that, you might need some extra processing...

shopt -s globstar
declare -A filelist=()

# Build a list...
for file in **; do filelist[$file]=1; done

# Remove PATTERNS...
while read -r glob; do
  for file in $glob; do
    unset filelist[$file]
  done
done < .rmignore

# And remove whatever's left.
echo rm -v "${!filelist[@]}"

And .. you guessed it. Untested. This depends on $f expanding as a glob.

Lastly, if you want a heavier-weight solution, you can use find and grep:

find . -type f -not -exec grep -q -f '{}' .rmignore \; -delete

This runs a grep for EACH file being considered. And it's not a bash solution, it only relies on find which is pretty universal.

Note that ALL of these solutions are at risk of errors if you have files that contain newlines.



回答3:

This line do perfectly the job

find . -type f | grep -vFf .rmignore


回答4:

If you have rsync, you might be able to copy an empty directory to the target one, with suitable rsync ignore files. Try it first with -n, to see what it will attempt, before running it for real!



回答5:

This is another bash solution that seems to work ok in my tests:

while read -r line;do 
exclude+=$(find . -type f -path "./$line")$'\n'
done <.rmignore

echo "ignored files:"
printf '%s\n' "$exclude"
echo "files to be deleted"
echo rm $(LC_ALL=C sort <(find . -type f) <(printf '%s\n' "$exclude") |uniq -u )  #intentionally non quoted to remove new lines

Test it online here



回答6:

Alternatively, you may want to look at the simplest format:

rm $(ls -1 | grep -v .rmignore)


标签: linux bash shell