0

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?

Rahul
  • 13,589
Sreeni
  • 9

3 Answers3

1

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 -.

Rahul
  • 13,589
1

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) and 2,$ 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 calls cp -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'
chaos
  • 48,171
1

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 the A option to ls; e.g., ls -ltrA.  (The order of the options doesn’t matter; you can use ls -ltAr, ls -lAtr, or even ls -ltr -A if you want to.)
  • I lied.  A long ls listing of a directory always has a total line first.  In ls -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, the ls might also exit, and you would avoid generating the listing for the entire directory.
    • You can eliminate the grep by doing

      ls -ltr | awk '! /^d/ { count++ } count==2 { print $NF; exit }'
      

      but I wouldn’t advise it.  Overly complex scripts are hard to maintain.

  • The -- after mv in the xargs 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/