Rm * excluding one file

Hi All,

I am trying to remove below file except the last one. With wild char It is removing all. Is there a way to exclude only one file and remove

files:

MM_6000_001
MM_6000_002
MM_6000_003
MM_6000_004

if I do

rm *

it is removing all. I want the exclude

and remove others. any way ?

ls -lrt  MM_6000_* | grep -v 004 

is getting me the desired out put in list not sure how can pass it to rm

# Overwrite $1 .. parameters with $1=file1, $2=file2, ..., $n=filen
set -- MM_6000_*

while [ "$#" -gt 1 ]
do
        echo rm "$1"
        shift # Set $2=$1, $3=$2, ... $n-1=$n
done

Remove the echo once you've tested and are sure it does what you want.

1 Like

With a recent bash , try the "extended pattern matching" after setting the option

shopt -s extglob
ls !(MM_6000_004)
3 Likes

Match MM_6000_ followed by all but 004
with bash

shopt -s extglob
ls MM_6000_!(004)

With zsh

setopt extendedglob
ls MM_6000_^004

Unless I'm missing something how about:

rm [-i -v] `ls -1 * | grep -v -e X -e Y -e X`
rm MM_6000_00[1-3]

Another option:

ls (parameters if needed) >my.file
(edit my.file to remove the specific file)
xargs <my.file rm

To test, replace "rm" with "echo" and it will list the files to be removed.

Note my.file should contain only file names. If it was a full listing, the vi command of "1,$s/^.* //" should fix it. Note the space after the asterisk.

A bit out of box and assuming that you want to retain the most recently modified file, rename the file you want to keep by giving it a leading dot, then remove everything else and rename the file back:-

# Work out file to be retained
save_file="$(ls -1rt |tail -1)"
mv "$save_file" ".$save_file"

# Delete everything else
rm *

# Move my file back
mv ".$save_file" "$save_file"

Would that work for you?

Perhaps also, list the files, stripping out the most recent:-

ls -1t | sed '1 d' | xargs rm

I hope that these help.
Robin

Hello Robin,

IMHO I think directly giving rm * could be dangerous in case we are not running the script in the same path where user actually wants to perform the actions. So I believe it will more good if we could add path here and check the condition if path exist too(my fav. Bakunin style :))

if [[ -d /my/path/for/actions_to_be_performed/ ]]
then
       cd /my/path/for/actions_to_be_performed/
       rm * ### Or whatever action needed by user
else
       echo "Path /my/path/for/actions_to_be_performed/ does not exist."
fi

Thanks,
R. Singh

Given the small number of files that you want to remove:

rm -i *

would suffice. Alternatively you could remove the write-attribute of the file(s) you wish to preserve. This would cause rm to prompt for their removal whilst quietly removing all other files. Just remember to put it back afterwards.

Andrew

If you have a varying list of files whose names all begin with MM_ and you want to remove all but the last name sorted alphanumerically, none of the filenames contain any IFS characters (by default <space>, <tab>, and <newline>), the list of files you're removing will never give you a command length that would exceed ARG_MAX , and you're using a shell that meets POSIX shell requirements (with or without extensions like shopt and setopt ). You could also try:

last=''
list=''

for file in MM_6000*
do	list="$list $last"
	last="$file"
done
[ ${#list} -gt 0 ] && rm $list

which is similar to what Corona688 suggested, but only invokes rm once instead of once for each file to be removed. (But his suggestion works without the limitations noted above except the 1st and last.)

Note: The problem noted by MadeInGermany in post #13 in this thread has been fixed above.

1 Like

So many ways to skin a cat!

Don Cragun's method using bash4 arrays:

files=(MM_6000*)
rm ${files[*]:0:${#files[*]}-1}

Same caveats as above, of course.

Andrew

Don's does not discard the last file, a correction is

list="" last=""
for file in MM_6000*
do
  list="$list $last"
  last=$file
done
[ ${#list} -gt 0 ] && rm $list

Another array variant (bash only):

files=(MM_6000*)
unset files[${#files[@]}-1] # delete the last array element
echo "${files[@]}"

(Replace echo with rm)

3 Likes

Not quite. If you run my (corrected) script twice, the 2nd execution will silently do nothing (since only one matching file remains). The code above will run rm with no file operands the 2nd time you run it (or any time that there are no files or only one file matching the pattern MM_6000* ). And MadeInGermany's code has this same issue.

The above can be fixed using:

files=(MM_6000*)
[ ${#files[*]} -gt 1 ] && rm ${files[*]:0:${#files[*]}-1}

and I think MadeInGermany's suggested code can be fixed with:

files=(MM_6000*)
unset files[${#files[@]}-1] # delete the last array element
[ ${#files[@]} -gt 0 ] && echo "${files[@]}"

again, replacing echo with rm if the resulting list of files produced is what you expect.

A slightly modified version of this also works with a recent Korn shell (tested with ksh version 93u+ 2012-08-01):

files=(MM_6000*)
unset files[$((${#files[@]}-1))] # delete the last array element
[ ${#files[@]} -gt 0 ] && echo "${files[@]}"

Both of the above suggestions work even with filenames that contain whitespace characters, but are still susceptible to ARG_MAX limits.

1 Like

Not only space characters - any special characters are handled well!

An example:
% touch MM_6000_1 MM_6000_2 "MM_6000_ 3" "MM_6000_*"
% printf "%s\n" MM_6000*
MM_6000_ 3
MM_6000_*
MM_6000_1
MM_6000_2
% files=(MM_6000*)
% unset files[$((${#files[@]}-1))] # delete the last array element
% [ ${#files[@]} -gt 0 ] && printf "%s\n" "${files[@]}"
MM_6000_ 3
MM_6000_*
MM_6000_1

The @ preserves the array elements in "${files[@]}" , just like it preserves the positional parameters in "$@" .