Recursively touch files with file - bash

I have a directory that contains sub-directories and other files and would like to update the date/timestamps recursively with the date/timestamp of another file/directory.
I'm aware that:
touch -r file directory
changes the date/timestamp for the file or directory with the others, but nothing within it. There's also the find version which is:
find . -exec touch -mt 201309300223.25 {} +\;
which would work fine if i could specify the actual file/directory and use anothers date/timestamp. Is there a simple way to do this? even better, is there a way to avoid changing/updating timestamps when doing a 'cp'?

even better, is there a way to avoid changing/updating timestamps when doing a 'cp'?
Yes, use cp with the -p option:
-p
same as --preserve=mode,ownership,timestamps
--preserve
preserve the specified attributes (default:
mode,ownership,timestamps), if possible additional attributes:
context, links, xattr, all
Example
$ ls -ltr
-rwxrwxr-x 1 me me 368 Apr 24 10:50 old_file
$ cp old_file not_maintains <----- does not preserve time
$ cp -p old_file do_maintains <----- does preserve time
$ ls -ltr
total 28
-rwxrwxr-x 1 me me 368 Apr 24 10:50 old_file
-rwxrwxr-x 1 me me 368 Apr 24 10:50 do_maintains <----- does preserve time
-rwxrwxr-x 1 me me 368 Sep 30 11:33 not_maintains <----- does not preserve time
To recursively touch files on a directory based on the symmetric file on another path, you can try something like the following:
find /your/path/ -exec touch -r $(echo {} | sed "s#/your/path#/your/original/path#g") {} \;
It is not working for me, but I guess it is a matter of try/test a little bit more.

In addition to 'cp -p', you can (re)create an old timestamp using 'touch -t'. See the man page of 'touch' for more details.
touch -t 200510071138 old_file.dat

Related

Replace a folder with another folder using mv (without deleting target folder first)

I am trying to get a directory to replace an existing folder but can't get it done with mv - I believe there's a way and I just don't know it (yet). Even after consulting the man page and searching the web.
If /path/to/ only contains directory, the following command will move /path/to/directory (vanishes) to /path/to/folder
mv /path/to/directory /path/to/folder
It is basically a rename, which is what I try to achieve.
But if /path/to/folder already exists, the same command moves the /path/to/directory to /path/to/folder/directory.
I do not want to use cp command to avoid IO.
Instead of using cp to actually copy the data in each file, use ln to make "copies" of the pointers to the file.
ln /path/to/directory/* /path/to/folder && rm -rf /path/to/directory
Note this is slightly more atomic than using cp; each individual file appears in /path/to/folder in a single step (i.e., there is no chance that /path/to/folder/foo.txt is ever partially copied), but there is still a small window where some, but not all, files from /path/to/directory have been linked to folder. Also, the rm -rf is not atomic, but assuming no one is interested in directory, that's not an issue. (Although, as files from /path/to/directory are unlinked, you can see changes to the link counts of files under /path/to/foldoer changing from 2 to 1. It's unlikely that anyone will care about that.)
What you think of as a file is really just a file system entry to an otherwise anonymous file managed by the file system. For example, consider a simple example.
$ mkdir d
$ cd d
$ echo hello > file.txt
$ cp file.txt file_copy.txt
$ ln file.txt file_link.txt
$ ls -li
total 24
12890456377 -rw-r--r-- 2 chepner staff 6 Mar 3 12:46 file.txt
12890456378 -rw-r--r-- 1 chepner staff 6 Mar 3 12:47 file_copy.txt
12890456377 -rw-r--r-- 2 chepner staff 6 Mar 3 12:46 file_link.txt
The -i option adds each entries inode number (the first column) to the output; an inode can be thought of as a unique identifier for a file. In this output, you can see that file_copy.txt is an entirely new file, with a different inode than file.txt. file_link.txt has the exact same inode, meaning that file.txt and file_link.txt are simply two different names for the same thing. The number just before the owner is the link count; file.txt and file_link.txt both refer to a file with a link count of 2.
When you use rm, you are just removing a link to a file, not the file itself. A file is not removed until the link count is reduced to 0. To demonstrate, we'll remove file.txt and file_copy.txt.
$ rm file.txt file_copy.txt
$ ls -li
total 8
12890456377 -rw-r--r-- 1 chepner staff 6 Mar 3 12:46 file_link.txt
As you can see, the only link to file_copy is gone, so inode 12890456378 no longer appears in the output. (Whether or not the data is really gone is a matter of file-system implementation.) file_link.txt, though, still refers to the same file as before, but now with a link count of 1, because file.txt was removed.
Links to a file do not have to appear in the same directory; they can appear in any directory on the same file system, which is the only caveat using this trick. ln will, IIRC, give you an error if you try to create a link to a file on another file system.

reliable way to delete older files in unix shell script

I'm trying to construct a reliable shell script to remove older files based on Xn of days using find. However, the script seems to work intermittently. Is there a better way? I list the files first to make sure I capture them, then use -exec rm{} to delete them.
I execute the script like so:
/home/scripts/rmfiles.sh /u05/backup/export/test dmp 1
#!/usr/bin/ksh
if [ $# != 3 ]; then
echo "Usage: rmfiles.sh <directory> <log|dmp|par> <numberofdays>" 2>&1
exit 1
fi
# Declare variables
HOURDATE=`date '+%Y%m%d%H%M'`;
CLEANDIR=$1;
DELETELOG=/tmp/cleanup.log;
echo "Listing files to remove..." > $DELETELOG 2>&1
/usr/bin/find $CLEANDIR -name "*.$2" -mtime +$3 -exec ls -ltr {} \; > $DELETELOG 2>&1
echo "Removing files --> $HOURDATE" > $DELETELOG 2>&1
#/usr/bin/find $CLEANDIR -name "*.$2" -mtime +$3 -exec rm {} \; > $DELETELOG 2>&1
My sample directory clearly has files older than one day as of today, but find is not picking it up when it was before during some previous testing.
Thu Sep 26 08:54:57 PDT 2013
total 161313630
-rw------- 1 oracle dba 10737418240 Sep 24 14:17 testexp01.dmp
-rw------- 1 oracle dba 10737418240 Sep 24 14:20 testexp02.dmp
-rw------- 1 oracle dba 10737418240 Sep 24 14:30 testexp03.dmp
-rw------- 1 oracle dba 508 Sep 24 15:41 EXPORT-20130924.log
-rw------- 1 oracle dba 509 Sep 25 06:00 EXPORT-20130925.log
-rw------- 1 oracle dba 508 Sep 26 08:30 EXPORT-20130926.log
Apart from a couple of small issues, the script looks good in general. My guess is that you want to add -daystart to the list of options so the base for the -mtime test is measured "from the beginning of today rather than from 24 hours ago. This option only affects tests which appear later on the command line."
If you have GNU find, then try find -D tree,search,stat,rates to see what is going on.
Some comments:
Always quote variables to make sure odd spaces don't have an effect: /usr/bin/find "$CLEANDIR" -name "*.$2" -mtime "+$3" .... Same with CLEANDIR="$1"
Don't terminate lines with ;, it's bad style.
You can replace -exec ls -ltr {} \; with -ls or -print. That way, you don't have to run the find command twice.
You should quote {} since some shells interpret them as special characters.
man find
-mtime mentions the read the comment at -atime
"When find figures out how many 24-hour periods ago the file was last accessed, any fractional part is ignored, so to match -atime +1, a file has to have been accessed at least two days ago." so this is also true for -mtime.

Bash get md5sum of all files in a folder

Hi I'm looking to see what file is changing in a directory i'd like to get the md5sum of every file and write it to a text file. Then after i know a file has changed i'd like to run it again so i can diff the output files to see what exactly changed. Here is what i've tried however it doesn't work as i need.
Also not only do i need to get the md5sum of every file in a folder including subdirectories i need it to not follow symlinks
#!/bin/bash
#
cd /sys/class
for i in $(find . -type f)
do
ls -lt "$i" >> /home/george/Desktop/before.txt
done
echo "Finished!"
Thank you for any help
===Edit===
I put my actual paths in as i don't really see a need to hide them. Anyway running this returned only a few files (outputted file below) which are the files in the folders meaning it's not going into subdirectories and finding those files too. Btw sorry my bash is way rusty
--w------- 1 root root 4096 Jun 20 03:03 ./gpio/export
--w------- 1 root root 4096 Jun 20 03:03 ./gpio/unexport
-rw-r--r-- 1 root root 4096 Jun 20 03:03 ./firmware/timeout
-r--r--r-- 1 root root 4096 Jun 20 03:04 ./drm/version
===Edit2===
Not exactly sure why some of these files aren't being found for instance
/sys/class/backlight/intel_backlight/brightness
And many others like that there are so many files that aren't being found for some reason
The cd is unnecessary, and with type -f you are already in fact bypassing symlinks. So the loop is unnecessary, too:
find /path/to/directory -type f -exec md5sum {} + >before.txt
If your find is too old to support -exec {} + try with -exec {} \; instead.
For the md5sum comparison, you could try simply removing identical lines;
fgrep -vxf before.txt after.txt | less
This is assuming the list in before.txt will fit into fgrep; but if you are dealing with a few dozen thousand files tops, it can probably cope. This will not identify deleted files from before.txt, though.
If your file list size is small enough that you can do it all in memory, you might consider sorting before.txt by the hash. If you do the same for after.txt you'd be able to go line by line on each of the files and identify matches even if the filename has changed. You'd also be able to skip over deleted or added files with less problems than if you had to interpret a diff before.txt after.txt
If using file modification date is an option, what you can do is use ls -lt | head to filter out the newest file and keep that. Then when you want to check for changes, ls -lt again and go through anything that's newer than the date you stored. This should work nicely regardless of file list size, but will be vulnerable to someone modifying last modification date (which would require root privileges)

Show full path when using options

I often use this list command in Unix (AIX / KSH):
ls -Artl
It displays the files as this:
-rw-r--r-- 1 myuser mygroup 0 Apr 2 11:59 test1.txt
-rw-r--r-- 1 myuser mygroup 0 Apr 2 11:59 test2.txt
I would like to modify the command such a way that the full path of the file is displayed. For example:
-rw-r--r-- 1 myuser mygroup 0 Apr 2 11:59 /usr/test1.txt
-rw-r--r-- 1 myuser mygroup 0 Apr 2 11:59 /usr/test2.txt
Any ideas?
I found several resolution methods using pwd or find but - as far as I see - this does not work work if I want to keep the ls options.
What about this trick...
ls -lrt -d -1 $PWD/{*,.*}
OR
ls -lrt -d -1 $PWD/*
I think this has problems with empty directories but if another poster has a tweak I'll update my answer. Also, you may already know this but this is probably be a good candidate for an alias given it's lengthiness.
[update] added some tweaks based on comments, thanks guys.
[update] as pointed out by the comments you may need to tweek the matcher expressions depending on the shell (bash vs zsh). I've re-added my older command for reference.
Try this, works for me: ls -d /a/b/c/*
Use this command:
ls -ltr /mig/mthome/09/log/*
instead of:
ls -ltr /mig/mthome/09/log
to get the full path in the listing.
I use this command:
ls -1 | xargs readlink -f
optimized from spacedrop answer ...
ls $(pwd)/*
and you can use ls options
ls -alrt $(pwd)/*
simply use find tool.
find absolute_path
displays full paths on my Linux machine, while
find relative_path
will not.
I wrote a shell script called fullpath that contains this code, use it everyday:
#!/bin/sh
for i in $* ; do
echo $(pwd)/$i
done
Put it somewhere in your PATH, and make it executable(chmod 755 fullpath) then just use
fullpath file_or_directory
You can combine the find command and the ls command. Use the path (.) and selector (*) to narrow down the files you're after. Surround the find command in back quotes. The argument to -name is doublequote star doublequote in case you can't read it.
ls -lart `find . -type f -name "*" `

Listing the content of a tar file or a directory only down to some level

I wonder how to list the content of a tar file only down to some level?
I understand tar tvf mytar.tar will list all files, but sometimes I would like to only see directories down to some level.
Similarly, for the command ls, how do I control the level of subdirectories that will be displayed? By default, it will only show the direct subdirectories, but not go further.
depth=1
tar --exclude="*/*" -tf file.tar
depth=2
tar --exclude="*/*/*" -tf file.tar
tar tvf scripts.tar | awk -F/ '{if (NF<4) print }'
drwx------ glens/glens 0 2010-03-17 10:44 scripts/
-rwxr--r-- glens/www-data 1051 2009-07-27 10:42 scripts/my2cnf.pl
-rwxr--r-- glens/www-data 359 2009-08-14 00:01 scripts/pastebin.sh
-rwxr--r-- glens/www-data 566 2009-07-27 10:42 scripts/critic.pl
-rwxr-xr-x glens/glens 981 2009-12-16 09:39 scripts/wiki_sys.pl
-rwxr-xr-x glens/glens 3072 2009-07-28 10:25 scripts/blacklist_update.pl
-rwxr--r-- glens/www-data 18418 2009-07-27 10:42 scripts/sysinfo.pl
Make sure to note, that the number is 3+ however many levels you want, because of the / in the username/group. If you just do
tar tf scripts.tar | awk -F/ '{if (NF<3) print }'
scripts/
scripts/my2cnf.pl
scripts/pastebin.sh
scripts/critic.pl
scripts/wiki_sys.pl
scripts/blacklist_update.pl
scripts/sysinfo.pl
it's only two more.
You could probably pipe the output of ls -R to this awk script, and have the same effect.
Another option is archivemount. You mount it, and cd into it. Then you can do anything with it just as with other filesystem.
$ archivemount /path/to/files.tgz /path/to/mnt/folder
It seems faster than the tar method.
It would be nice if we could tell the find command to look inside a tar file, but I doubt that is possible.
I quick and ugly (and not foolproof) way would be to limit the number of directory separators, for example:
$ tar tvf myfile.tar | grep -E '^[^/]*(/[^/]*){1,2}$'
The 2 tells to display not more than 2 slashes (in my case one is already generated by the user/group separator), and hence, to display files at depth at most one. You might want to try with different numbers in place of the 2.
I agree with leonbloy's answer - there's no way to do this straightforwardly within the tarball itself.
Regarding the second part of your question, ls does not have a max depth option. You can recurse everything with ls -R, but that's often not very useful.
However you can do this with both find and tree. For example to list files and directories one level deep, you can do
find -maxdepth 2
or
tree -L 2
tree also has a -d option, which recursively lists directories, but not files, which I find much more useful than -L, in general.
I was able to show only the directory names at a particular depth using grep:
for depth 3:
tar -tf mytar.tar | grep -Ex '([^/]+/){3}'
or for depth $DEPTH:
tar -tf mytar.tar | grep -Ex '([^/]+){$DEPTH}/'
You can speed that up by combining grep with --exclude from #sacapeao's accepted answer.
for depth 3:
tar --exclude '*/*/*/*/*' -tf mytar.tar | grep -Ex '([^/]+/){3}'

Resources