7

I want to copy and rename multiple c source files in a directory.

I can copy like this:

$ cp *.c $OTHERDIR

But I want to give a prefix to all the file names:

file.c --> old#file.c

How can I do this in 1 step?

17

a for loop:

for f in *.c; do cp -- "$f" "$OTHERDIR/old#$f"; done

I often add the -v option to cp to allow me to watch the progress.

7

You can use shell globbing:

for f in *.c; do cp -- "$f" "$OTHERDIR/old#$f"; done

The for variable in GLOB format will expand the glob to all matching files/directories (excluding hidden-ones) and iterate over them, saving each in turn as $variable (in the example above, $f). So, the command I show will iterate over all non-hidden files, copying them and adding the prefix.

  • If I do for f in /foo/bar/*.c, $f will contain the entire path, instead only the name of the file. How can I obtain only the name of the file? – alexandernst Aug 29 '19 at 16:05
  • 1
    @alexandernst either cd /foo/bar/; for f in *c, or use for f in /foo/bar/*.c; do name=${f##*/} ... or name=$(basename "$f"). – terdon Sep 6 '19 at 11:32
1

Still another way:

for f in $(find dir/ -type f | grep -v 'bk_'); do cp -- "$f" "$(dirname $f)/bk_$(pwgen -s 4 1)_$(basename $f)"; done

Each time you run this way, it will turn dir/ from:

dir/foo/foo.c
dir/bar/bar.c
dir/baz/baz.c

Into:

dir/foo/foo.c
dir/foo/bk_R3pP_foo.c
dir/bar/bar.c
dir/bar/bk_o1JD_bar.c
dir/baz/baz.c
dir/baz/bk_0QqF_baz.c

And then:

dir/foo/foo.c
dir/foo/bk_R3pP_foo.c
dir/foo/bk_dV06_foo.c
dir/bar/bar.c
dir/bar/bk_o1JD_bar.c
dir/bar/bk_B68i_bar.c
dir/baz/baz.c
dir/baz/bk_0QqF_baz.c
dir/baz/bk_p6P3_baz.c

And then...:

dir/foo/foo.c
dir/foo/bk_R3pP_foo.c
dir/foo/bk_dV06_foo.c
dir/foo/bk_2Ar8_foo.c
dir/bar/bar.c
dir/bar/bk_o1JD_bar.c
dir/bar/bk_B68i_bar.c
dir/bar/bk_wX7Z_bar.c
dir/baz/baz.c
dir/baz/bk_0QqF_baz.c
dir/baz/bk_p6P3_baz.c
dir/baz/bk_fPd3_baz.c

.. and so on.

0

Here's how one would do it recursively if needed,

for f in `find . -name \*.c`; do name="${f//\.\//__}"; fileName="${name//\//_}" ; echo "Copying $f as $OTHERDIR/old#$fileName"; cp -- "$f" "$OTHERDIR/$fileName"; done
0

Here is a one-liner using xargs, tr and globbing. Might have some value.

echo *.c | tr ' ' '\n' | xargs -n1 -I{} cp "{}" "PREFIX{}"

This returns all files matching *.c as a space-separated string. Next, tr turns the extra spaces into newlines (N.B. did not test file names with spaces**). Then, xargs gets populated with each file name, and runs cp with the appropriate name and prefix.

*.c can be modified for other, useful globs. Other prefixes in the xargs and cp part can be used as well.

**for those with spaces in their filenames:

(requires find that supports -print0) Similar to above, we can use find to output a null-seperated list of files, and tweak xargs with a flag to separate on null

find . -name '*.c' -print0 | xargs -0 -n1 -I{} cp "{}" "PREFIX{}"
  • 1
    And, if you had tested this with filenames containing spaces, you would have seen it fail. – Scott Nov 14 '18 at 19:44
  • 1
    Thanks for call-out. I knew it probably failed but thought I had already procrastinated through enough of my workday with the first pass. 😀 – TonyH Nov 15 '18 at 17:11

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.