Hack 37 Interactive Copy


figs/expert.gif figs/hack37.gif

When cp alone doesn't quite meet your copy needs.

The cp command is easy to use, but it does have its limitations. For example, have you ever needed to copy a batch of files with the same name? If you're not careful, they'll happily overwrite each other.

4.4.1 Finding Your Source Files

I recently had the urge to find all of the scripts on my system that created a menu. I knew that several ports used scripts named configure and that some of those scripts used dialog to provide a menu selection.

It was easy enough to find those scripts using find:

% find /usr/ports -name configure -exec grep -l "dialog" /dev/null {  } \; /usr/ports/audio/mbrolavox/scripts/configure /usr/ports/devel/kdesdk3/work/kdesdk-3.2.0/configure /usr/ports/emulators/vmware2/scripts/configure (snip)

This command asks find to start in /usr/ports, looking for files -named configure. For each found file, it should search for the word dialog using -exec grep. The -l flag tells grep to list only the names of the matching files, without including the lines that match the expression. You may recognize the /dev/null { } \; from [Hack #13] .

Normally, I could tell cp to use those found files as the source and to copy them to the specified destination. This is done by enclosing the find command within a set of backticks (`), located at the far top left of your keyboard. Note what happens, though:

% mkdir ~/scripts % cd ~/scripts % cp `find /usr/ports -name configure -exec grep -l "dialog" \      /dev/null {  } \;` . % ls ~/scripts configure

Although each file that I copied had a different pathname, the filename itself was configure. Since each copied file overwrote the previous one, I ended up with one remaining file.

4.4.2 Renaming a Batch of Source Files

What's needed is to rename those source files as they are copied to the destination. One approach is to replace the slash (/) in the original file's pathname with a different character, resulting in a unique filename that still reflects the source of that file.

As we saw in [Hack #15], sed is designed to do such replacements. Here's an approach:

% pwd /usr/home/dru/scripts % find /usr/ports -name configure -exec grep -l "dialog" /dev/null {  } \; \      -exec sh -c 'cp {  } `echo {  } | sed s:/:=:g`' \; % ls =usr=ports=audio=mbrolavox=scripts=configure =usr=ports=devel=kdesdk3=work=kdesdk-3.2.0=configure =usr=ports=emulators=vmware2=scripts=configure (snip)

This invocation of find starts off the same as my original search. It then adds a second -exec, which passes an argument -c as input to the sh shell. The shell will cp the source files (specified by { }), but only after sed has replaced each slash in the pathname with an equals sign (=). Note that I changed the sed delimiter from the default slash to the colon (:) so I didn't have to escape my / string. You don't have to use = as the new character; choose whatever suits your purposes.

awk can also perform this renaming feat. The following command is more or less equivalent to the previous command:

% find /usr/ports -name configure -exec grep -l "dialog" /dev/null {  } \; \     | awk '{dst=$0;gsub("/","=",dst); print "cp",$0,dst}' | sh

4.4.3 Renaming Files Interactively

Depending upon how many files you plan on copying over and how picky you are about their destination names, you may prefer to do an interactive copy.

Despite its name, cp's interactive switch (-i) will fail miserably in my scenario:

% cp -i `find /usr/ports -name configure -exec grep -l "dialog" \     /dev/null {  } \;` . overwrite ./configure? (y/n [n]) n not overwritten overwrite ./configure? (y/n [n]) (snip)

Since each file is still named configure, my only choices are either to overwrite the previous file or to not copy over the new file. However, both cpio and pax are capable of interactive copies. Let's start with cpio:

% find /usr/ports -name configure -exec grep -l "dialog" /dev/null {  } \; \      | cpio -o > ~/scripts/test.cpio && cpio -ir < ~/scripts/test.cpio

Here I've piped my find command to cpio. Normally, I would invoke cpio once in copy-pass mode. Unfortunately, that mode doesn't support -r, the interactive rename switch. So, I directed cpio to send its output (-o >) to an archive named ~/scripts/test.cpio. Instead of piping that archive, I used && to delay the next cpio operation until the previous one finishes. I then used -ir to perform an interactive copy in that archive so I could type in the name of each destination file.

Here are the results:

cpio: /usr/ports/audio/mbrolavox/scripts/configure: truncating inode number cpio: /usr/ports/devel/kdesdk3/work/kdesdk-3.2.0/configure: truncating  inode number cpio: /usr/ports/emulators/vmware2/scripts/configure: truncating inode number (snip other archive messages) 5136 blocks rename /usr/ports/audio/mbrolavox/scripts/configure -> mbrolavox.configure rename /usr/ports/devel/kdesdk3/work/kdesdk-3.2.0/configure ->  kdesdk3.configure rename /usr/ports/emulators/vmware2/scripts/configure -> vmware2.configure (snip remaining rename operations) 5136 blocks

After creating the archive, cpio showed me the source name so I could rename the destination file. While requiring interaction on my part, it does let me fine-tune exactly what I'd like to call each script. I must admit that my names are much nicer than those containing all of the equals signs.

pax is even more efficient. In the preceding command, the first cpio has to wait until find completes, and the second cpio has to wait until the first cpio finishes. Compare that to this command:

% find /usr/ports -name configure -exec grep -l "dialog" /dev/null {  } \; \     | pax -rwi .

Here, I can pipe the results of find directly to pax, and pax has very user-friendly switches. In this command, I asked to read and write interactively to the current directory. There's no temporary archive required, and everything happens at once. Even better, pax starts working on the interaction before find finishes. Here's what it looks like:

ATTENTION: pax interactive file rename operation. -rwxr-xr-x Nov 11 07:53 /usr/ports/audio/mbrolavox/scripts/configure Input new name, or a "." to keep the old name, or a "return" to skip  this file. Input > mbrovalox.configure Processing continues, name changed to: mbrovalox.configure

This repeats for each and every file that matched the find results.

4.4.4 See Also

  • man cp

  • man cpio

  • man pax



BSD Hacks
BSD Hacks
ISBN: 0596006799
EAN: 2147483647
Year: 2006
Pages: 160
Authors: Lavigne

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net