Reputation: 43
I want to check a folder for files and delete some of them. One condition is to keep all files of a certain type (e.g. .txt) and also keep all files with the names of the first search but with different extensions ([names of first search].). All other files in the directory should be deleted.
This can easily be achieved by the find . -type f -not -name xxx
command. However, I would like to populate the find command for each [name of first search] found automatically.
To do so I wrote this litte script
#!/bin/bash
while read filename; do
filename=$(echo $filename | sed 's/\ /\\\ /g')
filename=\'$filename*\'
file_list=$file_list" -not -name $filename"
done <<<"$(ls *.txt | sed 's/.txt//g')"
find . -type f $file_list -print0| while read -d $'\0' FILE
do
rm -f "$FILE"
done
The $file_list is nicely populated with the respective data, however, find fails saying:
find: unknown predicate `-\'
in case I use the sed command (' ' -> '\ ') or
find: paths must precede expression: - Usage: find [-H] [-L] [-P] [-Olevel] [-D [help|tree|search|stat|rates|opt|exec] [path...] [expression]
if I comment the sed line.
bash -x shows me the following executed command:
without the sed command:
find . -type f -not -name ''\''Text' - here - or - there*'\'''
with the sed command:
find . -type f -not -name ''\''Text\' '-\' 'here\' '-\' 'or\' 'there*'\'''
Is this even possible with find? I also tried escaping $find_list
in the find command with no success.
Upvotes: 1
Views: 3179
Reputation: 7656
Try this
#!/bin/bash
remove_except()
{
local extension=$( printf "%q" "$1" )
local dir=$( printf "%q" "$2" )
local start_dir=$(pwd)
[ -z "$extension" ] && return 1
[ -z "$dir" ] || [ ! -d "$dir" ] && dir="."
cd "$dir"
local this="$0"
this="${this##*/}"
# exclude myself and extension
local excludes=" -name \"$this\" -o -name \"*.$extension\" "
for f in *."$extension";
do
filename="${f%.*}"
excludes="$excludes -o -name \"$filename.*\""
done
eval "find . -maxdepth 1 -type f -not \( $excludes \) -print0" | xargs -0 -I {} rm -v {}
cd "$start_dir"
}
remove_except "txt" "/your/dir"
Put into a script e.g. remove_except.sh
and run it like this:
remove_except.sh "txt" "/your/dir"
The second argument is optional and will assume .
if not specified.
Upvotes: -1
Reputation: 295403
Use an array, not a string.
#!/bin/bash
# ^-- must be /bin/bash, not /bin/sh, for this to work
excludes=( )
for filename in *.txt; do
excludes+=( -not -name "${filename%.txt}" )
done
find . -type f -not -name '*.txt' "${excludes[@]}" -exec rm -f '{}' +
To understand why this works, see BashFAQ #50.
Now, if you want to be compatible with /bin/sh
, not just bash, then encapsulate this in a function so you can overwrite the argument list (which is the only available array) without throwing away the script's global arguments:
delete_except_textfiles() {
local filename 2>/dev/null ||: "local keyword not in POSIX, ignore if not present"
set --
for filename in *.txt; do
set -- "$@" -not -name "${filename%.txt}"
done
find . -type f -not -name '*.txt' "$@" -exec rm -f '{}' +
}
delete_except_textfiles
Upvotes: 3