The task is to write a shell script with n+1 arguments where the first one is the directory and the rest is the specified files and will delete all but specified files.

E.g. calling rmexcept . '*.jpg' '*.png'

cd $1
for i in “${@:2}”
find . -type f -not -name $i -delete

Here is my attempt. However, it just only works with 1 specified file (e.g. rmexcept . '*.jpg'). If there are more than 1 file (e.g. rmexcept . '*.jpg' '*.png'), all the files are deleted. I can't figure out what went wrong since I believe I have created a for loop.

  • 1
    Of course, every png file is not a jpg file, so those disappear, then down the line. Since it's an assignment, what was taught recently, so that we might guide you in a productive direction? – Jeff Schaller Apr 29 '19 at 2:38
  • Well, my recent lessons revolve around control structures (for, while, if, case, etc.), printf, expr, dirname and basename – user350473 Apr 29 '19 at 2:41
  • @Theophrastus erm... that doesn't work out very well :(. I got 'mv: rename *.pdf to /tmp/*.pdf: No such file or directory' (say, I want to delete all but pdf files) – user350473 Apr 29 '19 at 6:29

Try this (comments inline):

set -f     # Prevent e.g. *.txt from being expanded here

dir=$1     # Get the target directory and
shift      # remove from list of args

cmd="find $dir -type f"
while (( "$#" ))              # While there are more arguments left
    cmd="$cmd -not -name $1"  # add to not match
    shift                     # and remove from list of arguments
cmd="$cmd -exec rm -i {} ;"   # finally execute rm on the remaining matches

echo $cmd  # Print the final find command
$cmd       # And execute it

I added -i to rm so that it asks before removing each file. But you can tweak that, of course.

  • Thank you so much! It worked! – user350473 Apr 29 '19 at 9:35
  • 1
    This code won't finish the job if either of the resulting find and rm commands are longer than the number returned bygetconf ARG_MAX. See Bash command line and input limit – agc Apr 29 '19 at 12:57
  1. Same mechanism as laenkeio's answer, but as a more minimal no-variable POSIX shell function:

    rmexcept(){ find "$1" $(shift; printf " -not -name %s" ${@}) -exec rm '{}' \; ;}

    The shell's printf command does the main work, instead of a for or while loop.

  2. Using the bash shell, the shift can be replaced by a parameter expansion:

    rmexcept(){ find "$1" $(printf " -not -name %s" ${@:2}) -exec rm '{}' \; ;}

Note that neither function will finish the job if the resulting find or rm commands are longer than the number returned by getconf ARG_MAX. See: Bash command line and input limit.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy