49

I have a bash script for deploying code from a beta environment to a production environment but currently I have to add the list of files to a txt file manaully and sometime I miss some. Basically my deployment script cat/loops copying the files over. (exports/imports db as well but that's not relevant..lol)

Anyway, I'd like to use the find command to generate a list of files modified in the last 14 days. The problem is I need to strip the path out ./ in order for the deployment script to work.

Here's an example of the find command usage:

find . -type f -mtime -14 > deploy.txt

Here's the line that cats deploy.txt in my deployment script:

for i in `cat deploy.txt`; do cp -i /home/user/beta/public_html/$i /home/user/public_html/$i; done

Any idea how to accomplish this using bash scripting?

Thanks!

3
  • 3
    Is there a reason you can't just use rsync for this? It sounds as if you may be trying to reinvent the wheel and make it a triangle. Jan 27, 2012 at 21:05
  • valid point however before deployment the deploy.txt is reviewed and anything that needs fixing gets removed
    – Mikey1980
    Jan 27, 2012 at 21:08
  • 2
    Maybe run rsync in --dry-run mode and output that to a file for review?
    – Zoredache
    Jan 27, 2012 at 21:14

8 Answers 8

55

You can use the -printf command line option with %f to print just the filename without any directory information

find . -type f -mtime -14 -printf '%f\n' > deploy.txt

or you can use sed to just remove the ./

find . -type f -mtime -14 | sed 's|^./||' >deploy.txt
9
  • 6
    Not when you use | as the delimiter.
    – user9517
    Jan 27, 2012 at 21:13
  • 5
    Using %f will strip off all directories, not just ./. This will break his deployment script if any of the files returned by find are located in subdirectories. Jan 27, 2012 at 21:13
  • 2
    @Iain You do need to escape the . though. And maybe best to use a start-of-line anchor: s|^\./|| Jan 27, 2012 at 21:25
  • 23
    Use %P instead of %f. This just cut off the path given in the command line.
    – TrueY
    May 29, 2013 at 10:05
  • 3
    This seems like a nice, but Linux-only trick. Regular find(1) -- such as that found on FreeBSD, for example, does not have the -printf predicate...
    – Mikhail T.
    Mar 22, 2018 at 20:26
14

The ./ should be harmless. Most programs will treat /foo/bar and /foo/./bar as equivalent. I realize it doesn't look very nice, but based on what you've posted, I see no reason why it would cause your script to fail.

If you really want to strip it off, sed is probably the cleanest way:

find . -type d -mtime 14 | sed -e 's,^\./,,' > deploy.txt

If you're on a system with GNU find (e.g. most Linux systems), you can do it in one shot with find -printf:

find . -type d -mtime 14 -printf "%P\n" > deploy.txt

The %P returns the complete path of each file found, minus the path specified on the command line, up to and including the first slash. This will preserve any subdirectories in your directory structure.

3
  • 1
    The ./ prefix is actually a feature; without it, piping output to other commands will not work correctly e.g. for file names which start with a dash.
    – tripleee
    Nov 10, 2017 at 5:21
  • 1
    @tripleee - I understand the utility of it, but OP specifically asked how to strip it off because it was causing problems for his deployment script. Sure, there might be better ways to do what he needs to do, but that's a rabbit hole well beyond the scope of what was asked. Nov 18, 2017 at 16:32
  • rsync for example won't in some situations when you use find to cretae pattern files.
    – Mecki
    Sep 28, 2020 at 22:03
7

"find -printf" solution won't work on FreeBSD because find don't have such an option. In this case AWK can help. It returns a last name ($NF), so it can work on any depth.

find /usr/local/etc/rc.d -type f | awk -F/ '{print $NF}'

PS: taken from D.Tansley "Linux and Unix shell programming" book

7

Why do you need to strip off the ./? It is valid to have it in the middle of a path. So

cp -i dir1/./somefile dir2/./somefile

is just OK!

But if you would like to strip off the directory name using find, you can use the %P arg to -printf.

man find(1) says:

         %P     File's name with the name of the  command  line  argument
                 under which it was found removed.

An example

$ find other -maxdepth 1
other
other/CVS
other/bin
other/lib
other/doc
other/gdbinit
$ find other -maxdepth 1 -printf "%P\n"

CVS
bin
lib
doc
gdbinit

Mind the first empty line! If you want to avoid it use -mindepth 1

$ find other -mindepth 1 -maxdepth 1 -printf "%P\n"
CVS
bin
lib
doc
gdbinit
1
  • Yes, it is valid when specifying paths to be interpreted by the system but it will change behavior when using the find output to generate file list for other tools, e.g. rsync include/exclude lists.
    – Mecki
    Sep 28, 2020 at 22:02
4

Well you have a few options. You could use the -printf option in find to only print out the filename, or you could use a tool like sed to simply strip out the ./.

# simply print out the filename, will break if you have sub-directories.
find . -mtime -14 -printf '%f\n'

# strip a leading ./
find .  -mtime -14 | sed -e 's/^\.\///'
3
  • Can you give me an example of it's usage? Sorry, linux is not really my forté
    – Mikey1980
    Jan 27, 2012 at 21:11
  • 2
    Look again, there are examples.
    – Zoredache
    Jan 27, 2012 at 21:13
  • Agreed - just try it, you're not going to hurt anything running a combination of find and sed.
    – EEAA
    Jan 27, 2012 at 21:15
3

A quick solution, if I understand correctly the question, is using cut on the output of the find command:

$> find . -type f -mtime -14 | cut -b 3- > deploy.txt

This will strip the first to characters from each line of the result (in your case ./). Probably not the best solution, but works in your scenario.

1

sed is perfect for this sort of thing.

$ find . -type f -mtime -14 find . | sed 's/^\.\///' > deploy.txt
1
  • Small tip: sed allows you to use any other character instead of / as separator. When working with paths, this is pretty useful as it requires less escaping: sed 's|^\./||'
    – Mecki
    Jan 7, 2020 at 11:05
1

I don't know what kind of filenames we're talking about, but anything with spaces or newlines puts most of the solutions here at risk.

My suggestion would be to use shell's parameter expansion to strip of the characters from each filename:

find . -mtime -14 -exec sh -c 'printf "${0#./}\n"' {} \; >deploy.txt

If you really really like pipes, you could use a "safe" delimiter, like a null, with xargs to receive each filename:

find . -mtime -14 -print0 | xargs -0 -n 1 sh -c 'printf "${0#./}\n"' >deploy.txt

Remember that the output of this is NOT null-delimited, so while it may be sufficient for an eyeball check, none of these solutions produce a deploy.txt file that is safe to use for automation, unless you're very confident about your source filenames.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .