How to view file date of result of find command in bash - bash

I use a find command to find some kinds of files in bash. Everything goes fine unlness the result that is shown to me just contains the file name but not the (last modification) date of file. I tried to pipe it into ls or ls -ltr but it just does not show the filedate column in result, also I tried this:
ls -ltr | find . -ctime 1
but actually I didn't work.
Can you please guide me how can I view the filedate of files returned by a find command?

You need either xargs or -exec for this:
find . -ctime 1 -exec ls -l {} \;
find . -ctime 1 | xargs ls -l
(The first executes ls on every found file individually, the second bunches them up into one ore more big ls invocations, so that they may be formatted slightly better.)

If all you want is to display an ls like output you can use the -ls option of find:
$ find . -name resolv.conf -ls
1048592 8 -rw-r--r-- 1 root root 126 Dec 9 10:12 ./resolv.conf
If you want only the timestamp you'll need to look at the -printf option
$ find . -name resolv.conf -printf "%a\n"
Mon May 21 09:15:24 2012

find . -ctime 1 -printf '%t\t%p\n'
prints the datetime and file path, separated by a ␉ character.

Related

find the last created subdirectory in a directory of 500k subdirs

I have a folder with some 500k subfolders - and I would like to find the last directory which was added to this folder. I am having to do this due to a power failure issue :(
I dont excatly know when the power failed, so using this:
find . -type d -mmin -360 -print
which I beleive is the last 360 minutes? However, gives me results which I am not exactly sure of.
Shortly speaking, I would like to get the last directory which was created within this folder.
Any pointers would be great.
Suggesting :
find . -type d -printf "%C# %p\n" |sort -n|tail -n1|awk '{print $2}'
Explanation:
find . -type d -printf "%C# %p\n"
find . start searching from current directory recursively
-type d search only directory files
-printf "%C# %p\n" for each directory print its last change time in secs from Unix epoch time including sec fraction, followed by file name with path.
For example: 1648051886.4404644000 /tmp/mc-dudi
|sort -n|tail -n1
Sort the result from find as numbers, and print the last row.
awk '{print $2}'
From last row, print only second field
You might try this: it shows your last modification date/time in a sortable manner, and by sorting it, the last entry should be the most recent one:
find ./ -exec ls -dils --time-style=long-iso {} \; | sort -k8,9
Edit: and specific for directories:
find ./ -type d -exec ls -dils --time-style=long-iso {} \; | sort -k8,9
Assuming you're using a file system that tracks file creation ('birth' is the usual terminology) times, and GNU versions of the programs used below:
find . -type d -exec stat --printf '%W\t%n\0' \{\} + | sort -z -k1,1nr | head -1 -z | cut -f 2-
This will find all subdirectories of the current working directory, and for each one, print its birth time (The %W format field for stat(1)) and name (The %n format). Those entries are then sorted based on the timestamp, newest first, and the first line is returned minus the timestamp.
Unfortunately, GNU find's -printf doesn't support birth times, so it calls out to stat(1) to get those, using the multi-argument version of -exec to minimize the number of instances of the program that need to be run. The rest is straightforward sorting of a column, using 0-byte terminators instead of newlines to robustly handle filenames with newlines or other funky characters in them.
Mantaining a symbolic link to the last known subdirectory could avoid listing all of them to find the latest one.
ls -dl $(readlink ~/tmp/last_dir)
drwxr-xr-x 2 lmc users 4096 Jan 13 13:20 /home/lmc/Documents/some_dir
Find newer ones
ls -ldt $(find -L . -newer ~/tmp/last_dir -type d ! -path .)
drwxr-xr-x 2 lmc users 6 Mar 1 00:00 ./dir2
drwxr-xr-x 2 lmc users 6 Feb 1 00:00 ./dir1
Or
ls -ldt $(find -L . -newer ~/tmp/last_dir -type d ! -path .) | head -n 1
drwxr-xr-x 2 lmc users 6 Mar 1 00:00 ./dir2
Don't use the chosen answer if you really want to find the last created sub-directory
According to the question:
Directories should be sorted by creation time instead of modification time.
find --mindepth 1 is necessary because we want to search only sub-directories.
Here are 2 solutions that both fulfill the 2 requirements:
GNU
find . -mindepth 1 -type d -exec stat -c '%W %n' '{}' '+' |
sort -nr | head -n1
BSD
find . -mindepth 1 -type d -exec stat -f '%B %N' '{}' '+' |
sort -nr | head -n1

Basename removes the second file of my 'find' list in bash

I have run into an issue when using basename on a list of files found with find.
For some reason, basename decides to delete the second item of the find list when there are only two files to be found.
gas$ ls -l
total 8
-rwxrwxrwx 1 gas staff 54 Feb 26 19:00 find_sh.sh
-rw-r--r-- 1 gas staff 0 Feb 26 19:21 test-file.sh
gas$ find . -type f -name '*.sh'
./find_sh.sh
./test-file.sh
gas$ basename $(find . -type f -name '*.sh')
find_sh.sh
Adding a third file seems to fix it for an obscure reason (see below)
gas$ touch test-file2.sh
gas$ basename $(find . -type f -name '*.sh')
find_sh.sh
test-file.sh
test-file2.sh
Does anybody know what happens for the basename of the second file? I'm currious about what's going wrong.
I have found as a solution to ditch basename and simply use a sed 's/.*\///' to remove the path in the name.
So even if I found a solution, I'm still curious about what's going wrong with basename when there are two files to be displayed :)
Thanks in advance!
You don't need basename at all, find is capable on its own:
find . -type f -name '*.sh' -printf '%f\n'
The reason your 2 argument form of basename is not working like you expect is because that's how the 2 argument form of basename works. From the man page:
SYNOPSIS
basename NAME [SUFFIX]
basename OPTION... NAME...
When given no options, the 2nd argument is the suffix to strip off of the first argument.

List the files that I can read

I would like to list any files that can be read by my current user in bash.
I'm not sure what would be the best way to check for that. I'm thinking something along the lines of ls -l | grep <myusername>|<mygroupname> or find ., but that doesn't deal with the other permissions.
Also, I'm working on NetBSD box.
Considering the 2 files below, where one can be read by user, and the other can't:
[fsilveir#fsilveir tmp]$ ls -l ./test_dir/can_read.txt ./test_dir/cant_read.txt
-rw-r--r--. 1 root root 861784 May 29 20:34 ./test_dir/can_read.txt
-rwx------. 1 root root 0 May 29 20:30 ./test_dir/cant_read.txt
You can use find with -perm option. By using +r you'll list the files you can read, and using -r for finding the ones you can't read, as shown below:
[fsilveir#fsilveir tmp]$ find . -name "*.txt" -perm -g+r 2>/dev/null
./test_dir/can_read.txt
[fsilveir#fsilveir tmp]$
Another approach is using find with -readable option, as shown below:
[fsilveir#fsilveir tmp]$ find . -name "*.txt" -readable
./test_dir/can_read.txt
[fsilveir#fsilveir tmp]$ find . -name "*.txt" ! -readable
./test_dir/cant_read.txt

Copying all the files modified this month from the command line

I want to copy all the files in a directory that were modified this month. I can list those files like this:
ls -l * | grep Jul
And then to copy them I was trying to pipe the result into cp via xargs but had no success (I think) because I couldn't figure out how to parse the ls -l output to just grab the filename for cp.
I'm sure there are many ways of doing this; I'll give the correct answer out to the person who can show me how to parse ls -l in this manner (or talk me down from that position) though I'd be interested in seeing other methods as well.
Thanks!
Of course, just doing grep Jul is bad because you might have files with Jul in their name.
Actually, find is probably the right tool for your job. Something like this:
find $DIR -maxdepth 1 -type f -mtime -30 -exec cp {} $DEST/ \;
where $DIR is the directory where your files are (e.g. '.') and $DEST is the target directory.
The -maxdepth 1 flag means it doesn't look inside sub-directories for files (isn't recursive)
The -type f flag means it looks only at regular files (e.g. not directories)
The -mtime -30 means it looks at files with modification time newer than 30 days (+30 would be older than 30 days)
the -exec flag means it executes the following command on each file, where {} is replaced with the file name and \; is the end of the command
interested in seeing how this might be done with zsh
ls -lt *.*(mM0)
last month
ls -lt *.*(mM1)
or for precise date date ranges
autoload -U age
ls -tl *.*(e#age 2014/06/07 now#)
ls -tl *.*(e#age 2014/06/01 2014/06/20#)

Linux "for" construct in ksh

I normally use the for construct in ksh to quickly iterate over a list of files to perform some action on it. It doesn't seem to work in this scenario:
The file info looks like:
$ ls -l tmp.*
rw------- 1 op general 375 Jul 25 04:09 tmp.zzyhsg4
...
so on. Basically a lot of tmp.* files.
Now when I try
$ ls -lS | grep 'Jul 25' | grep 'tmp.*' | cut -d' ' -f9 | more
tmp.zzyhsg4
..
it will print only the file names as expected.
However when I try the below
$ for i in `ls -lS | grep 'Jul 25' | grep 'tmp.*' | cut -d' ' -f9`
>do
>echo $i
>done
This does not print the name of all the files starting with tmp.* which were created on Jul 25 sorted by size. It prints the size column. Interestingly if I replace the f10 by f6 for the cut it will correctly print the month column. It starts to break after f9.
Any ideas ?
I can't reproduce exactly what you describe, but I have some suggestions to write more reliable commands.
cut -d' ' separates fields by spaces. If you have two spaces in a row, there's an empty field between them. So if you try with Aug 1 instead of Jul 25, the file name column is shifted by 1. And if you try with files that are more than 6 months old, the (5-character) time is replaced by a space followed by the 4-digit year. Also, depending on your version of ls there may be more than one space between some columns. Yet another issue is that some versions of ls don't display the group column. And then some file names contain spaces. And some file names contain special characters that ls may display as ?. In summary, you can't parse the output of ls -l by counting spaces, and you can't even parse the output of ls -l by counting whitespace-delimited fields. Just don't parse the output of ls.
The standard command for generating lists of names of existing files is find. Since you mention Linux, I'll mention options that work with GNU find (the version you get on Linux) but not on other unixes.
Let's start simple: list the files called tmp.* in the current directory.
find . -name 'tmp.*'
We want only the files created on July 25, that's 7 days ago.
find . -name 'tmp.*' -daystart -mtime 7
This is fragile since it won't work tomorrow. The usual way to specify a precise date is to create files dated at the earliest and latest allowable times and tell find to only return files dated between these two.
touch -t 201007250000 .earliest
touch -t 201007260000 .latest
find . -name 'tmp.*' -newer .earliest \! -newer .latest
rm .earliest .latest
The find command explores subdirectories recursively. If you don't want this:
find . -name 'tmp.*' -daystart -mindepth 1 -maxdepth 1 -mtime 7
If you want the files sorted by size:
find . -name 'tmp.*' -daystart -mtime 7 -printf '%s\t%p\n' | sort -n -k 1 | cut -f 2-
Finally, if you want to operate on the files, never use find in backticks, the way you used ls, because this will fail if the file names contain whitespace or some special characters, because the shell splits the output of `command` at whitespace and then does globbing on the resulting words. Instead, use the -exec option to find; the ; version executes mycommand once per file with {} replaced by the file name, whereas the + version usually invokes mycommand only once with {} replaced by the list of file names.
find . -name 'tmp.*' -daystart -mtime 7 -exec mycommand -myoption {} \;
find . -name 'tmp.*' -daystart -mtime 7 -exec mycommand -myoption {} +
To get all files named tmp.*, use
$ ls -lS tmp.*
With the cut you tell to take the space as a delimiter. That will not work properly. The number of spaces between fields is flexible, so you will have a varying number of fields. (between every 2 spaces you will have an empty field)
Better use find, which can shape your file list to any form you like:
$ find tmp.* -printf "%s %CD %f\\n" | grep "07/25/10" | sort -n
(man find to also get the date filtering within the find command)

Resources