You are not logged in.
Hey guys, just added this little thing to my bashrc when I got tired of "rm -rf" ' ing a bunch of files when I only wanted to keep one or two in the directory:
EDIT: Small change to handle keeping directories
function keep(){
for file in * ; do
kfile="FALSE"
if [ -d "$file" ] ; then
file=${file#/}
fi
for keep in $@; do
if [ "$keep" == "$file" ] ; then
kfile="TRUE"
break
fi
done
if [ "$kfile" == "FALSE" ] ; then
rm -rf "$file"
fi
done
}
Works like this:
[woodape@abox keeper]$ls
keep1 keep2 one one1 one2 one3 one4
[woodape@abox keeper]$keep keep1 keep2
[woodape@abox keeper]$ls
keep1 keep2
It can probably be cleaned up, let me know if you have a suggestion. Hope its useful to someone, enjoy!
Last edited by woodape (2016-02-10 12:43:52)
Offline
Always double quote $@ to prevent word splitting!
for keep in "$@"
You could make the args of the function keep regular expression patterns in stead of strings that have to match exactly. That would make it a lot more versatile.
Since it is meant to be used for bash, use [[ in stead of [.
Offline
Ah, thanks for that @xaos52, I missed that double quote. My regex experience is pretty limited, how would you go about adding it in there?
Offline
This seems to work, but probably needs more testing:
function keep(){
for file in * ; do
kfile="FALSE"
if [ -d "$file" ] ; then
file=${file#/}
fi
for pattern in "$@"; do
if [[ "$file" == $pattern ]] ; then
kfile="TRUE"
break
fi
done
if [[ "$kfile" == "FALSE" ]] ; then
rm -rf "$file"
fi
done
}
# Tests
mkdir ./t5_tests
cd ./t5_tests
touch a b c keep1 keep2 keep3
ls -al
keep 'keep*'
ls -al
As you can see in the test, you can now run
keep 'keep*'
to remove all files, except the ones starting with 'keep'
Offline
I see what you mean, however, just running
keep keep*
would have done the same thing with the original code. Bash expansion happens before the script is run, so keep* is passed as keep1 keep2 keep3. Maybe this style would be more useful with more complicated regex?
Offline
Maybe this style would be more useful with more complicated regex?
Yes, everything regexes offer more than just path name globbing.
Offline
To use regexes, rather than the shell globbing above, you need the comparison operator =~ and the syntax is different, so for the same result as the 'keep*' glob the regex would be 'keep.*'
I don't think the code
if [ -d "$file" ] ; then
file=${file#/}
fi
is doing anything, as it would remove a slash from the beginning of the string where there is not going to be one.
I've dropped the kfile variable and used continue 2 to move on to the next file, so the whole thing can be simplified to
keep(){
for file in *
do for pattern in "$@"
do
if [[ "$file" =~ $pattern ]]; then
continue 2
fi
done
echo "rm -rf $file"
done
}
I replaced the rm -f command with an echo so you can test what it would do without actually removing any files. 'rm -rf' in a script or function scares me to be honest. I'd be very careful with this!
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
With any of these methods, be very careful with your glob-patterns/regexes. If you make a mistake and the regex matches nothing, then all the files will be deleted. Safest to do a dry-run first.
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
Thanks for all the comments guys!
@johnraff I think using "continue" as you did is particularly useful. The "rm -rf" line is scary I suppose, but so is "rm -rf". My use-case for this function is typically in my "~/tmp" folder where a bunch of useless (but not all useless) stuff tends to accumulate and me using tab completion following "keep" to add the files I want to hang on to. I think I'll hang onto the
if [ "$file" == "$pattern" ]; then
and keep all your other changes. Its good to know that if I end up getting more comfortable with regex I can incorporate it easily.
@nobody, that is some nifty stuff going on, but I'm not sure if I'm there yet, I'd have to get more comfortable with those commands first.
In terms of a dry run, what do you guys think about this?:
EDIT: Thinking about it in terms of safety, the dry run should be default and the "-d" should stand for delete instead
keep(){
del="F"
if [ "$1" == "-d" ] ; then
del="T"
shift
fi
for file in *
do for pattern in "$@"
do
if [[ "$file" =~ $pattern ]]; then
continue 2
fi
done
if [ "$del" == "T" ] ; then
rm -rf "$file"
else
echo "rm -rf $file"
fi
done
}
Last edited by woodape (2016-02-13 07:50:08)
Offline
You could also allow passing the "rm" options directly to the "keep" command. "--" separates the rm arguments from the file/pattern list in my solution below.
#!/usr/bin/env bash
keep() {
local RM_OPTS FILES
while [[ -n $1 && $1 != "--" ]]; do
RM_OPTS="$RM_OPTS $1"
shift
done
if [[ -z $1 ]]; then
FILES=$RM_OPTS
RM_OPTS=""
else
shift
FILES="$@"
fi
for file in *; do
for pattern in $FILES; do
if [[ "$file" =~ $pattern ]]; then
continue 2
fi
done
rm -rf $RM_OPTS "$file"
done
}
keep "$@"
Then you could call the script with
keep -i -- keep1 keep2
to run rm in interactive mode.
This way you can omit the "-r" option to "rm" from the script so that users can decide for themselves if "keep" script should run recursively or not.
Last edited by olminator (2016-02-13 18:31:13)
Offline
Even with the -r option to rm the script will not run recursively. ie it will not look inside sub-directories, choosing files for deletion. It will simply decide whether or not to delete the whole parent directory, depending on its name. Without -r it won't delete directories at all.
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline