I had n
number of files are there in my ../in/
directory. From those files I want the oldest file to process and send it to the ../complete/
directory. The remaining files are sent to the ../error/
directory. Can anyone help?
3 Answers
To find oldest file and move that to your desired directory, use:
# cd to ../in/
$ ls -lt | grep -v '^d' | tail -1 | awk '{print $NF}' | xargs -I '{}' mv '{}' ../complete/
Now that your oldest file has been moved out, you can move all files to ../error/
$ mv -- * ../error/
The --
after mv
is necessary if you have any file/directory names starting with a -
.

- 13,589
To copy the oldest file to ../complete
:
cp -v "$(find ../in -maxdepth 1 -type f -printf '%T@ %p\0' | sort -zn | \
sed -zn '1s/[0-9,\.]\+ //p')" ../complete
To copy the all except the oldest to ../error
:
find ../in -maxdepth 1 -type f -printf '%T@ %p\0' | sort -zn | \
sed -zn '2,$s/[0-9,\.]\+ //p' | xargs -0 cp -vt ../error
Explanation:
find
-maxdepth 1
descends only 1 level; therefore it will not search the directory recusively.-type f
match only files.-printf '...'
a custom output format.%T@
gives the file's last modification time in a timestamp,%p
is the file's name. Notice the\0
in the end. The files are printed null byte delimited, not newline delimited, because the newline could probably be a character in the file's name, which would then break the command.
sort -zn
this will read the output null byte delimited (-z
) and sort it numerically (-n
).sed -zn
reads the output null byte delimited.1
will only match the first line (first command) and2,$
will match all others (second command)s/[0-9,\.]\+ //p
will remove the leading timestamp from the output.
- From the second command only:
xargs -0 cp -vt ../error
processes the output null byte delimited (-0
) and callscp -vt ../error
on all input, which copies the files.
Example output:
$ ls -lt ../in/
total 0
-rw-r--r-- 1 user user 0 Jun 24 08:18 file2
-rw-r--r-- 1 user user 0 Jun 24 08:18 file3
-rw-r--r-- 1 user user 0 Jun 24 08:18 file4
-rw-r--r-- 1 user user 0 Jun 24 08:18 file5
-rw-r--r-- 1 user user 0 Jun 24 08:17 file_oldest
$ cp -v "$(...)" ../complete
'../in/file_oldest' -> '../complete/file_oldest'
$
$ find ... | xargs ... ../error
'../in/file2' -> '../error/file2'
'../in/file3' -> '../error/file3'
'../in/file4' -> '../error/file4'
'../in/file5' -> '../error/file5'

- 48,171
Building on Rahul’s answer:
$ ls -ltr | grep -v '^d' | awk 'NR==2 {print $NF; exit}' | xargs -I '{}' mv -- '{}' ../complete/
Notes:
- You say you want to do something with “the oldest file”. All the answers are assuming that you mean least recently modified. If you mean least recently changed, say so; the answers will be slightly different. If you mean least recently created, what you want is almost impossible in Unix.
ls -lt
(what Rahul used) lists the current directory in long format in order by modification date/time, with the newest first and the oldest last.ls -ltr
is the reverse of that; oldest first and the newest last.- If you might have files whose names begin with a period (
.
), add theA
option tols
; e.g.,ls -ltrA
. (The order of the options doesn’t matter; you can usels -ltAr
,ls -lAtr
, or evenls -ltr -A
if you want to.) - I lied.
A long
ls
listing of a directory always has atotal
line first. Inls -ltr
, the oldest entry is on the second line. grep -v '^d'
filters out the directories (if any).awk 'NR==2 { print $NF; exit }'
prints the last field (the filename*) from the second line (the oldest file) and then exits.- This is one fewer process than
tail -1 | awk '{print $NF}'
. - This might work faster, because,
when the
awk
exits, thels
might also exit, and you would avoid generating the listing for the entire directory. You can eliminate the
grep
by doingls -ltr | awk '! /^d/ { count++ } count==2 { print $NF; exit }'
but I wouldn’t advise it. Overly complex scripts are hard to maintain.
- This is one fewer process than
- The
--
aftermv
in thexargs
command is necessary if you have any file names starting with a-
. You don’t really need the
xargs
. You can simplify the above command to$ mv -- $(ls -ltr | grep -v '^d' | awk 'NR==2 { print $NF; exit }') ../complete/
________
* Like Rahul’s answer, this will fail if the filename contains space(s) or tab(s) — and even worse if it contains newline(s).
If you might have files whose names begin with a period (.
),
do shopt -s dotglob
before doing the
$ mv -- * ../error/