Trying to Delete All But Most Recent 2 Files in Sub Directories

Trying to delete all but most recent 2 files in sub directories

Unlike the existing answer, this one NUL-delimits the output from find, and is thus safe for filenames with absolutely any legal character -- a set which includes newlines:

delete_all_but_last() {
local count=$1
local dir=${2:-.}
[[ $dir = -* ]] && dir=./$dir
while IFS='' read -r -d '' entry; do
if ((--count < 0)); then
filename=${entry#*$'\t'}
rm -- "$filename"
fi
done < <(find "$dir" \
-mindepth 1 \
-maxdepth 1 \
-type f \
-printf '%T@\t%P\0' \
| sort -rnz)
}

# example uses:
delete_all_but_last 5
delete_all_but_last 10 /tmp

Note that it requires GNU find and GNU sort. (The existing answer also requires GNU find).

Delete All But The Newest File of Pattern In Each Subdirectory

If you want to remove all files LIVE_DATA_* except the most recent one on a per-folder basis you could do something like this:

$root = 'C:\path\to\root\folder'

Get-ChildItem $root -Recurse | ? { $_.PSIsContainer } | ForEach-Object {
Get-ChildItem (Join-Path $_.FullName 'LIVE_DATA_*') |
Sort-Object Name -Desc |
Select-Object -Skip 1 |
Remove-Item -Force
}

Get-ChildItem $root -Recurse | ? { $_.PSIsContainer } lists all subfolders of $root. Then the ForEach-Object runs another Get-ChildItem statement (without recursion) for each subfolder separately. The Join-Path statement builds a wildcard path from the filename pattern and the full path to the folder (C:\path\to\root\folder\sub\folder\LIVE_DATA_*).

Basically the code lists all folders, then processes the files for each individual folder.

How to delete all but the most recent files in a directory of folders and files

You can try this:

$Path = "C:\Users\user\Desktop\TEST"
$Folders = Get-ChildItem $Path
foreach ($Folder in $Folders)
{
$FolderName = $Folder.FullName
$Files = Get-ChildItem -Path $FolderName
$FileNumber = $Files.Count - 1
$Files | sort CreationTime -Descending | select -last $FileNumber | Remove-Item -Force -WhatIf
}

Delete all but the most recent X files in bash

For Linux (GNU tools), an efficient & robust way to keep the n newest files in the current directory while removing the rest:

n=5

find . -maxdepth 1 -type f -printf '%T@ %p\0' |
sort -z -nrt ' ' -k1,1 |
sed -z -e "1,${n}d" -e 's/[^ ]* //' |
xargs -0r rm -f

For BSD, find doesn't have the -printf predicate, stat can't output NULL bytes, and sed + awk can't handle NULL-delimited records.

Here's a solution that doesn't support newlines in paths but that safeguards against them by filtering them out:

#!/bin/bash
n=5

find . -maxdepth 1 -type f ! -path $'*\n*' -exec stat -f '%.9Fm %N' {} + |
sort -nrt ' ' -k1,1 |
awk -v n="$n" -F'^[^ ]* ' 'NR > n {printf "%s%c", $2, 0}' |
xargs -0 rm -f

note: I'm using bash because of the $'\n' notation. For sh you can define a variable containing a literal newline and use it instead.


Solution for UNIX & Linux (inspired from AIX/HP-UX/SunOS/BSD/Linux ls -b):

Some platforms don't provide find -printf, nor stat, nor support NUL-delimited records with stat/sort/awk/sed/xargs. That's why using perl is probably the most portable way to tackle the problem, because it is available by default in almost every OS.

I could have written the whole thing in perl but I didn't. I only use it for substituting stat and for encoding-decoding-escaping the filenames. The core logic is the same as the previous solutions and is implemented with POSIX tools.

note: perl's default stat has a resolution of a second, but starting from perl-5.8.9 you can get sub-second resolution with the stat function of the module Time::HiRes (when both the OS and the filesystem support it). That's what I'm using here; if your perl doesn't provide it then you can remove the ‑MTime::HiRes=stat from the command line.

n=5

find . '(' -name '.' -o -prune ')' -type f -exec \
perl -MTime::HiRes=stat -le '
foreach (@ARGV) {
@st = stat($_);
if ( @st > 0 ) {
s/([\\\n])/sprintf( "\\%03o", ord($1) )/ge;
print sprintf( "%.9f %s", $st[9], $_ );
}
else { print STDERR "stat: $_: $!"; }
}
' {} + |

sort -nrt ' ' -k1,1 |

sed -e "1,${n}d" -e 's/[^ ]* //' |

perl -l -ne '
s/\\([0-7]{3})/chr(oct($1))/ge;
s/(["\n])/"\\$1"/g;
print "\"$_\"";
' |

xargs -E '' sh -c '[ "$#" -gt 0 ] && rm -f "$@"' sh

Explanations:

  • For each file found, the first perl gets the modification time and outputs it along the encoded filename (each newline and backslash characters are replaced with the literals \012 and \134 respectively).

  • Now each time filename is guaranteed to be single-line, so POSIX sort and sed can safely work with this stream.

  • The second perl decodes the filenames and escapes them for POSIX xargs.

  • Lastly, xargs calls rm for deleting the files. The sh command is a trick that prevents xargs from running rm when there's no files to delete.

Recursively delete all but the one newest file throughout all directories

I think the following script should do the trick for you.

#!/bin/bash

DIR_TO_FIND="/path/to/dir"

find "$DIR_TO_FIND" -type d | while read -r DIR; do
cd "$DIR"
ls -t | tail -n +2 | xargs -d '\n' rm -f
cd "$DIR_TO_FIND"
done

delete all but the last match

#!/bin/bash
shopt -s globstar
for dir in **/; do
files=("$dir"file*)
unset 'files[-1]'
rm "${files[@]}"
done

delete all but X most recent folders

This will keep the 10 latest log files based on modification date:

@echo off
for /f "skip=10 delims=" %%a in (' dir *.log /o-d /a-d /b ') do echo del "%%a"

Remove the echo to make it perform the deletions rather than just display them.



Related Topics



Leave a reply



Submit