How to iterate through all files in a directory, ordered by date created, with some filenames have spaces in their names
Use find
in combination with xargs
to pass file names with NUL-byte separation, and use a while
read loop for efficiency and space preservation:
find /path/to/dir -type f -print0 | xargs -0 ls -t | while read filedo ls "$file" # or whatever you want with $file, which may have spaces # so always enclose it in double quotesdone
find
generates the list of files, ls
arranges them, by time in this case. To reverse the sort order, replace -t
with -tr
. If you wanted to sort by size, replace -t
with -s
.
Example:
$ touch -d '2015-06-17' 'foo foo'$ touch -d '2016-02-12' 'bar bar'$ touch -d '2016-05-01' 'baz baz'$ ls -1bar barbaz bazfoo foo$ find . -type f -print0 | xargs -0 ls -t | while read file> do> ls -l "$file"> done-rw-rw-r-- 1 bishop bishop 0 May 1 00:00 ./baz baz-rw-rw-r-- 1 bishop bishop 0 Feb 12 00:00 ./bar bar-rw-rw-r-- 1 bishop bishop 0 Jun 17 2015 ./foo foo
For completeness, I'll highlight a point from comments to the question: -t
is sorting by modification time, which not strictly creation time. The file system on which these files reside dictates whether or not creation time is available. Since your initial attempts used -t
, I figured modification time was what you were concerned about, even if it's not pedantically true.
If you want creation time, you'll have to pull it from some source, like stat
or the file name if its encoded there. This basically means replacing the xargs -0 ls -t
with a suitable command piped to sort
, something like: xargs -0 stat -c '%W' | sort -n
Using GNU find
and GNU sort
, one can do the following:
while IFS='' read -r -d ' ' mtime && IFS='' read -r -d '' filename; do printf 'Processing file %q with timestamp of %s\n' "$filename" "$mtime"done < <(find "$dir" -type f -printf '%T@ %p\0' | sort -znr)
This works as follows:
find
prints its output in the format<seconds-since-epoch> <filename><NUL>
.sort
sorts that numerically -- thus, by modification time, expressed in seconds since epoch.IFS='' read -r -d ' ' mtime
reads everything up to the space into the variablemtime
.IFS='' read -r -d '' filename
reads all remaining content up to the NUL into the variablefilename
Because NUL cannot exist in filenames (as compared to newlines, which can), this can't be thrown off by names with surprising contents. See BashFAQ #3 for a detailed discussion.
Moreover, because it doesn't depend on passing names as command-line arguments to ls -t
(which, like all other external commands, can only accept a limited number of command-line arguments on each invocation), this approach is not limited in the number of files it can reliably sort. (Using find ... -exec ls -t {} +
or ... | xargs ls -t
will result in silently incorrect results when the number of filenames being processed grows larger than the number that can be passed to a single ls
invocation).
You can temporarily set your IFS variable to avoid the problem with spaces (thanks to http://www.linuxjournal.com/article/10954?page=0,1)
IFS_backup=$IFSIFS=$(echo -en "\n\b")for file in `ls -t dir` ; do #blahdoneIFS=$IFS_backup
Edit: this worked on Ubuntu, but not RHEL6. The alternative suggested by bishop appears to be more portable, for example:
ls -t dir|while read file; do ...; done