You are not logged in.

#1 2016-02-10 12:29:49

woodape
Member
Registered: 2015-09-29
Posts: 38

Keep Function for Bash -- sorta the opposite of rm

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

#2 2016-02-10 12:48:40

xaos52
The Good Doctor
From: Planet of the @pes
Registered: 2015-09-30
Posts: 695

Re: Keep Function for Bash -- sorta the opposite of rm

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

#3 2016-02-10 13:08:43

woodape
Member
Registered: 2015-09-29
Posts: 38

Re: Keep Function for Bash -- sorta the opposite of rm

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

#4 2016-02-10 13:25:57

xaos52
The Good Doctor
From: Planet of the @pes
Registered: 2015-09-30
Posts: 695

Re: Keep Function for Bash -- sorta the opposite of rm

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

#5 2016-02-10 14:10:49

woodape
Member
Registered: 2015-09-29
Posts: 38

Re: Keep Function for Bash -- sorta the opposite of rm

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

#6 2016-02-10 14:24:54

xaos52
The Good Doctor
From: Planet of the @pes
Registered: 2015-09-30
Posts: 695

Re: Keep Function for Bash -- sorta the opposite of rm

Maybe this style would be more useful with more complicated regex?

Yes, everything regexes offer more than just path name globbing.

Offline

#7 2016-02-12 06:26:41

johnraff
nullglob
From: Nagoya, Japan
Registered: 2015-09-09
Posts: 12,652
Website

Re: Keep Function for Bash -- sorta the opposite of rm

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 )

Introduction to the Bunsenlabs Boron Desktop

Offline

#8 2016-02-13 04:33:07

johnraff
nullglob
From: Nagoya, Japan
Registered: 2015-09-09
Posts: 12,652
Website

Re: Keep Function for Bash -- sorta the opposite of rm

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 )

Introduction to the Bunsenlabs Boron Desktop

Offline

#9 2016-02-13 07:26:02

woodape
Member
Registered: 2015-09-29
Posts: 38

Re: Keep Function for Bash -- sorta the opposite of rm

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

#10 2016-02-13 18:29:43

olminator
Member
Registered: 2015-11-24
Posts: 10

Re: Keep Function for Bash -- sorta the opposite of rm

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

#11 2016-02-16 03:51:37

johnraff
nullglob
From: Nagoya, Japan
Registered: 2015-09-09
Posts: 12,652
Website

Re: Keep Function for Bash -- sorta the opposite of rm

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 )

Introduction to the Bunsenlabs Boron Desktop

Offline

Board footer

Powered by FluxBB