List all files and directories in the current directory separated by commas and sorted by crtime

What I know so far:
ls -A will list all files except those starting with a dot
ls -d will list all directories
ls -m will separate contents by commas
For getting crtimes use:
stat filename will give me the inode number
or
ls -i filename will give me the inode number

df -h

will give me the root partition
Then I can use:

sudo debugfs -R 'stat <inode>' root partion | grep crtime

to get the crtime

I need to know how to tie all of this together.

Hi, try so

ls -cm

--- Post updated at 14:39 ---

what does this word mean "crtime" ?

--- Post updated at 14:50 ---

option -c in the ls command sorts files by the time of their last modification, but if this is a directory, then this is the time of the last modification of files in it.

Hi nezabudka,
For file systems that keep track of it, "crtime" refers to the time at which a file was created.

The last modification time (sometimes just called "mtime") of a directory is usually the time that the directory was created, the last time a link to a file was created in that directory, or the last time a link to a file was removed from that directory, whichever occurred most recently. But, of course, it can also be set to an arbitrary time at least by the C language futimens( ), utimensat( ), and utimes() functions. Changing the size of an already existing file in a directory does not change the modification time of any directory that contains that file.

Note that if a file has multiple hard links (not symlinks), that single file can exist in more than one directory.

1 Like

I won't address all points because i am a bit short on time today. It suffices for addressing one point, though:

No. What -d does is it will not follow a directory. That means if you use a wildcard (or, more precisely, a "file glob") like this:

ls foo*

The shell will - prior to calling ls - expand the glob into a list of filesystem items matching this name. filesystem items can be all sorts of things but we are here interested only in files and directories. So, lets say for example that there are three such items, fooA , fooB and fooC . If these are all files the output would simply be:

$ ls foo*
fooA fooB fooC

So far, so good. But what happens if one of these is a directory? In this case the directory would be followed, which means all the files in this directory would be displayed too, i.e.

$ ls foo*
fooA

fooB:
 fileinfooB1 fileinfooB2

fooC:
fileinfooC1 fileinfooC2

Notice that this is not the fault of the shell: the shell still generates only the list of the three filenames but ls , when it sees the name of a directory, will list the files in that directory. In this case obviously fooB and fooC must be directories. This makes sense because when you enter

ls /usr

You usually want to see what is in that directory, not just the directory name itself. But sometimes one would not want that and this is why -d exists. It will make the ls NOT follow the directories and display their files but only there names as if they would be normal files.

I hope this helps.

bakunin

I have been able to make some progress on the problem. I found a partial solution, but I can't
post the links yet.

Here is the code:

xstat() {
  for target in "${@}"; do
    inode=$(ls -di "${target}" | cut -d ' ' -f 1)
    fs=$(df "${target}"  | tail -1 | awk '{print $1}')
    crtime=$(sudo debugfs -R 'stat <'"${inode}"'>' "${fs}" 2>/dev/null | 
    grep -oP 'crtime.*--\s*\K.*')
    printf "%s\t%s\n" "${crtime}" "${target}"
  done
}

I made a change to the printf statement to:

printf "%s %s, " "${crtime}" "{target}"

With this change I get a comma separated list of crtime (creation time) and names of the files
in the current directory.
The problem with this is that the files are not sorted by crtime.

I found code that solved the sort problem, but I don't know how to change the printf statements to get a comma separted list of crtime and name:

Here is the code I found:

crtime-at() {
for target in *; do
inode=$(ls -di "${target}" | cut -d ' ' -f 1)
fs=$(findmnt -n -o SOURCE --target "${target}")
crtime=$(sudo debugfs -R 'stat <'"${inode}"'>' "${fs}" 2>/dev/null | 
grep -oP 'crtime.*--\s*\K.*')
crtime=$(date -d "${crtime}" +%Y-%m-%d\ %H:%M:%S)
printf "%s\t%s\n" "${crtime}" "${target}" >> /tmp/crtime
done
crtime=$(sort -t$'\t' -k1 -n /tmp/crtime)
printf "${crtime}"
rm /tmp/crtime
echo
}

The second program sorts by crtime, but I don't know enough about
Bash programming to figure out how to get i displayed as a comma separated list.

If anyone understands this code and has the time to explain it line by line, I believe it
will help me as well as the community.

Thanks for all the responses.

Charles

Hmm I clearly must be missing something. Late to the party again...
stat command will do what you want without sudo.

%w is the human readable format for crtime
%W is the format for epoch seconds for crtime -- which you use for sorting.

$ stat --format "%W, %w, %n" t.lis
1440371010, 2015-08-23 17:03:30.103891500 -0600, t.lis

So, this may be what you want: get three fields, sort by field one (crtime), print readable crtime and file name with comma separated data. Change the awk printf to what you want. Seemed a bit confusing to me.

cd /path/to/files
 stat --format "%W, %w, %n" * | sort -n -k1 | awk -F ',' '{ printf("%s, %s", $2, $3)}' > /path/to/output/file

This looked promising and even the manpage for stat seems to agree, but when I enter the commands in ubuntu, all I get is:
-, filename
for each file.

Maybe I'm doing something wrong?

I doubt that this will work on Ubuntu systems either, but it might be worth a try. The operating system I'm using doesn't have the debugfs or findmnt utilities and the date utility doesn't have a -d option, but the following code seems to do what I think you're trying to do.

crtime-at() {
	stat -t '%Y-%m-%d %H:%M:%S' -f '%SB%t%N%n' * | sort | awk '
		NR > 1 {printf("%s,", last) }
		{	last = $0 }
		END {	print last }'
}

if your shell allows hyphen characters in function names (which is not portable and is not required by the standards) and your system has a BSD based version of the stat utility.

Unfortunately, the stat utility is not specified by the standards either so methods used to print a file's creation time (if the file system the files reside on keeps a file's creation time [which is not required by the standards]) varies from system to system and the methods used to specify strftime() -like format strings for printing dates varies from system to system (with some systems not providing creation dates nor any way to get the date/time format you want).

If nothing else, maybe the above code will show you how to use awk to convert a sorted text file into a single line output with input lines separated by commas instead of <newline>s.

This was tested on macOS Mojave version 10.14.3 with a BSD-based stat utility (probably with macOS extensions). Note that this version just sorts on the YYYY-mm-dd HH:MM:SS date/time stamps instead of trying to get a finer grained timestamp using UNIX Epoch times.

I have no idea what the stat utility man page on your system says about printing file creation dates or about ways to specify the format of dates that are to be printed.

Hey Don!

If you need a Linux server to log into and try commands I have a Linode Ubuntu server I can create an account for you.

Let me know!

Hi Tim,
It's too late tonight (i.e. this morning) for me. I need some sleep.

But, if you e-mail me details on how to login to your Ubuntu system, I'll play around with it later today.

Cheers,
Don

Thanks Don!
I'll also like to try your system out tomorrow.
In the meantime, I will make the changes you suggested.

It's good to have so much help. This is the way to code -:slight_smile:

On (plain) Ubuntu systems, you won't get a file's crtime from stat , as its %w "format sequence" supplies 0, or "-", respectively:

Access: 2019-03-13 11:43:18.362189910 +0100
Modify: 2019-03-09 20:25:05.000000000 +0100
Change: 2019-03-13 11:48:32.571558428 +0100
Birth: -

The debugfs crtime entry might be closer to the desired result, but isn't it strange that it is later than mtime ?

  ctime: 0x5c88e000:88452870 -- Wed Mar 13 11:48:32 2019
  atime: 0x5c88dec6:565a5158 -- Wed Mar 13 11:43:18 2019
  mtime: 0x5c841311:00000000 -- Sat Mar  9 20:25:05 2019
 crtime: 0x5c84136a:ba74cad4 -- Sat Mar  9 20:26:34 2019

You could simply pipe your result list through

 | paste -sd,

Here is a slightly optimized version of your code that should run a little bit faster and produce the output I think you're trying to get.

#!/bin/bash
crtime-at() {
	fs=$(findmnt -n -o SOURCE --target "$PWD")
	ls -id * | while read -r inode target
	do
		crtime=$(sudo debugfs -R "stat <$inode>" "${fs}" 2> /dev/null |
		    grep -oP 'crtime.*--\s*\K.*')
		crtime=$(date -d "$crtime" '+%Y-%m-%d %H:%M:%S')
		printf '%s\t%s\n' "$crtime" "$target"
	done | sort | paste -sd,
}
crtime-at
cd /tmp
crtime-at

This was tested on Ubuntu running release 4.15.0-32-generic. Unfortunately, as noted by RudiC on this Ubuntu release, the stat utility only shows - for the birth time (aka struct stat crtime field) of a file and the ls utility has no option to print (or sort) on the creation timestamp of a file even if the underlying file system stores this data. I used the paste as RudiC suggested since it should be more efficient than the awk code I suggested in an earlier post in this thread. I didn't observe the mtime before crtime on any of the files I observed on Ubuntu, but that abnormality certainly could be created with a C or C++ program or using the touch utility.

If you want to switch to using mtime instead of crtime you could just use stat and make your loop much simpler as suggested before by using:

 stat --printf='%y\t%n\n' * | sort | paste -sd,

If you don't like the sub-seconds and the offset from GMT in the output that produces, those artifacts could very simply be removed if you go back to a slightly modified version of the awk code I suggested instead of using paste .

I moved the check for the filesystem out of the loop since all files in a directory are by definition in the same filesystem. And, we can get the inode numbers and the file names names from ls directly without needing to invoke ls a second time and invoking cut inside the loop to extract it. The two invocations of your function after its definition were used by me to verify the output I was getting. Feel free to replace that code with whatever else you were doing in your script.

If there's anything in this version of your function that you don't understand, ask and I'll try to explain it for you.

I hope this helps.

Just for the fun of it (and be aware that eval comes with its known problems):

for FN in *; do  eval $(stat -c"sudo debugfs -R 'stat <%i>' \$(grep %m /etc/mtab| cut -d' ' -f1)" $FN) 2>/dev/null | sed -n "/^crtime.*--/{s///;s/$/ $FN/p}" ; done  | paste -sd,

Unfortunately, stat and debugfs aren't mutually compatible, e.g. in the fields that they use and output.