Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to iterate through all files in a directory, ordered by date created, with some filenames have spaces in their names

First I had

for file in `ls -t dir` ; do
  #blah
done

but files with spaces are split into two iterations.

I've found tons of variations on this that fix the spaces issue, but then leaves some date info in the $file variable.

Edit: to show one such variation:

for file in `find . -printf "%T@ %Tc %p\n" | sort -n` ; do
  #blah
done

The problem with this is that all the time info is still in-place within the $file variable in the loop. (also, this doesn't work because I happen to be on OSX, whose find utility lacks the -printf option...)

like image 452
Phildo Avatar asked Jun 13 '16 15:06

Phildo


People also ask

How do you handle file names with spaces in Linux?

Dealing with space in folder name To cd into a directory with space, use quotes or backslash again. Basically, whenever you have to deal with spaces in names, you use quotes or backslash keys.


2 Answers

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 variable mtime.
  • IFS='' read -r -d '' filename reads all remaining content up to the NUL into the variable filename

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

like image 96
Charles Duffy Avatar answered Oct 06 '22 01:10

Charles Duffy


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 file
do
    ls "$file" # or whatever you want with $file, which may have spaces
               # so always enclose it in double quotes
done

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 -1
bar bar
baz baz
foo 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

like image 34
bishop Avatar answered Oct 06 '22 02:10

bishop