0

I'm having trouble using a while loop to delete files until a certain amount of files remain.

This is what I have:

touch while/151234
touch while/152355
touch while/151694
touch while/153699
touch while/156946
NUMSNAPS=$(ls while | awk '{print $1}' | wc -l)
RETAIN=2

while [ "$RETAIN" -le "$NUMSNAPS" ]; do
    OLDEST=$(ls | awk '{print $1}' | head -n 1)
    rm "$OLDEST"
done

I cannot use find to delete these files as this is really a test to use with another third-party command. I plan on looking for snapshots with DOCTL and deleting them until a certain one remains.

cinemafunk
  • 13
  • 3

3 Answers3

1

You have a loop that tests a certain condition based on two variables that you never change. I'm assuming you were meaning to decrease the NUMSNAPS value by one in each iteration.


In the zsh shell, you may do

retain=2

rm -f while/*(DN.Om[retain+1,-1])

to delete all regular files (including files with hidden names) apart from the $retain most recently modified files. If this happens to be several thousands of files, you may want to use zargs instead:

retain=2

autoload -U zargs
zargs -- while/*(DN.Om[retain+1,-1]) -- rm

If you want to order the files by name instead, then replace Om ("order by mtime timestamp, in decreasing order") by On ("order by name, in decreasing order").

You could obviously call this from another shell than zsh by using e.g.

retain=2

zsh -c 'rm -f $2/*(DN.Om[$1+1,-1])' zsh "$retain" "while"

This also passes the name of the directory and the number of files to retain into the in-line script, making it easier to incorporate into an existing non-zsh script.


In the bash shell, you don't really want to parse the output of ls. Instead you may want to use

shopt -s dotglob nullglob

retain=2

set -- while/*

while [ "$#" -gt "$retain" ]; do
    rm -f "$1"
    shift
done

This would set the list of positional parameters to the list of files in the while directory (including hidden names), and then remove files form that list until there are only retain files left in the list.

This avoids issues with awkward filenames, and it also avoids calling ls once for every file removed.

This code would delete files based on their names (not by last-modified timestamp). This seems to be what your code is also attempting to do.

Kusalananda
  • 333,661
1

But why you can't get whole list and just keep number of files you need? For example:

ls -r while | tail -n +${retain} | xargs echo rm
  • 1
    The author of question will not work with ls and files but with some output of doctl. And my idea in general is not to remove items one by one and counting number of items left but get full list of items, skip number of elements we want to keep and just remove all others. – Fedor Dikarev Jan 03 '20 at 09:44
0

Here is how I would fix and improve your script:

#!/bin/bash

touch while/151234
touch while/152355
touch while/151694
touch while/153699
touch while/156946

NUMSNAPS=$(ls -1 while | wc -l)
RETAIN=2

while (( RETAIN < NUMSNAPS )); do
    OLDEST=$(ls -1t while | tail -n 1)
    rm "while/$OLDEST"
    (( --NUMSNAPS ))
done
Kusalananda
  • 333,661
fpmurphy
  • 4,636