1

I was trying to do something following,

find . -name "*.dat" | get the basename of file | move filename returned by first command to basename returned by second command

To give a concrete example, I want ./mydir1/dir2/file1.dat to be renamed as file1.dat .

Can I do it using pipes. If yes, how to I store output of first command in a variable in first pipe and use it in second pipe. I hope that I am using the term pipe correctly.

  • @Anthon Fixed the typo. – Dilawar May 21 '13 at 8:07
2

You don't need pipes for that the find command in itself is capable of doing that:

find . -name "*.dat" -exec mv -t . {} \;

Notice that this is somewhat inefficient as the .dat files already in the current directory are found and moved as well.

  • 1
    You'll probably want to use either xargs or -exec ... + as not to spawn a new process for every single matching file found. – peterph May 21 '13 at 11:57
  • 2
    Yes -t is GNU specific and only makes sense if you're using + instead of ;. So -exec mv -t . {} + (efficient but GNU specific) or -exec mv {} . \; (inefficient but portable). (or -exec sh -c 'exec mv "$@" .' sh {} +: portable, efficient but long-winded). – Stéphane Chazelas May 21 '13 at 12:05
2

You can't really influence environment "horizontally" in the pipe - the processes in a ... p_n | p_n+1 | p_n+2 ... pipe are spawned by the same shell interpreter, hence there is no way to change environment variable of say p_n from p_m, echii is in the same pipeline.

If you just need to do a simple transformation of the filenames, which can be achieved with regular expressions, then the Perl example rename should do. You might want/need to use xargs to prevent problems with escaping

find ... -print0 | xargs -0 rename "regexps"

which delimits the filenames with NUL bytes (NUL and backslash are the only characters that are generally forbidden to appear in a filename).

If your file names are well-behaved (and hence you can assume, that you don;'t need any special escaping of "weird" characters like quotes, spaces and separators used in the regexps), you can also do something quick and dirty like:

find ... | sed -r "regexps" | sh -

In this case the regexps have to create a valid command invoking mv (or something else that does the desired action) - it may look like:

"s|^.*$|mv -vi & &.old|"

which produces something like

...
mv -vi /etc/a2ps.cfg /etc/a2ps.cfg.old
mv -vi /etc/aclocal_dirlist /etc/aclocal_dirlist.old
...

If the simple regular expression is not powerful enough or would be too cumbersome, just write a simple script that processes filenames and does the rename/move itself and call it either from find directly (-exec or -exec +) or through xargs.

Finally, if you just want to move copy bunch of files to another directory, use the -t option of cp and mv. cp in the GNU coreutils also has the --parents option, which copies the source with its complete path.

0

In an effort to solve the real problem in as robust a way as possible:

WARNING: very long commandline.

find "${directory:-.}" -type f -name "*.dat" -exec sh -c 'for dat; do 
                                                                            if [[ -e $dat ]]; then
                                                                              base_fn=${dat%.*}$((++n)).dat
                                                                              base_fn=${dat##*/}
                                                                            else
                                                                              base_fn=${dat##*/}
                                                                            fi
                                                                            mv "$dat" "$base_fn"
                                                                           done' _ {} +

This is portable; POSIX actually specifies this -exec ... {} + as a replacement for the most common use of xargs. The GNU specific mv -t is nice to have around, I suppose. The only problem to note is that, without GNU's mv -t, you are required to spawn a new mv process for every file.

On the other hand, flattening a directory structure risks clobbering files which have the same basename. My method will avoid that except in the most extreme situation (if you have more files than you can fit into ARG_MAX, and in the second invocation of sh you run across another duplicate basename which was in the first invocation, then you're going to lose the first one.) oh well. such an awful corner-case to code around.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.