3

I have a directory structure with multiple .txt files with multiple directories. I want to copy all these .txt files into one particular directory. I have given below my directory structure:

Dir_1
    a.txt
    x.doc
    b.txt
Dir_2
    c.txt
Dir_3
    d.txt
    e.txt
    y.doc

To copy all .txt files into the directory ~/alltxt, I am using the below cp command.

find . -name "*.txt" -exec cp {}  ~/alltxt/  \;

It just works fine. Now my directory alltxt is like below.

alltxt
        a.txt
        b.txt
        c.txt
        d.txt
        e.txt

But I see that I want to replace ONLY the changed .txt files into that folder. So, I came to know that I could do that using rsync command like below.

find .  -name '*.txt' -exec rsync -R {} ~/alltxt/ \;

But it copies .txt files folder by folder like below:

alltxt
  Dir_1
    a.txt
    x.doc
    b.txt
  Dir_2
    c.txt
  Dir_3
    d.txt
    e.txt
    y.doc

But How can I change my rsync command to work like my cp command?

2

rsync has a flag, --existing, which will avoid copying files that does not already exist on the target:

find . -type f -name '*.txt' -exec rsync --existing {} "$HOME/alltxt/" ';'

I have also added -type f to make sure that find only finds regular files, and I have removed the -R flag (--relative) from rsync because I don't think it's useful in this case (using this flag is also the reason why you get the subdirectories on the target). I'm also using $HOME rather than ~ because I think it looks nicer.

You may also want to add -a (--archive) to the rsync invocation to preserve ownership and modification times etc.

A similar approach, but using cp instead of rsync:

find . -type f -name '*.txt' \
    -exec sh -c 'for n; do d="$HOME/alltxt/${n##*/}"; [ -f "$d" ] && cp "$n" "$d"; done' sh {} +

This finds all .txt files and calls a short shell script:

for n; do
    d="$HOME/alltxt/${n##*/}"
    [ -f "$d" ] && cp "$n" "$d"
done

This loop gets a number of .txt files from find. For each such name n, it creates the destination file path in d, checks whether the this path refers to an existing regular file and if so, copies the given file there.

Instead of the test [ -f "$d" ] you could use [ "$d" -ot "$n" ] which will be true if the destination file exists and is older than the found file. This is a nonstandard test but is supported by most shells.

The variable substitution ${n##*/} will remove the directories from the path in n, so that ./some/path/file.txt is turned into just file.txt (the base name). It could be replaced by $(basename "$n").

0
shopt -s globstar
cp --update --target-directory targetDirectory sourceRootDir/**/*.txt

where:

  • shopt -s globstar sets the extended globbing options, which include the ** pattern
  • --target-directory or -t gives a target directory to copy to (it can also be specified at the end of all the args, but it is often more readable to put it here in scripts).
  • --update or -u makes cp copy the file only if the target is older i or non-existent.
  • The -u option would create new files. This is not what the user wants (as far as I understand it). – Kusalananda Feb 9 '18 at 8:28
  • Also, there is (currently) no indication that the user is using GNU coreutils. – Kusalananda Feb 9 '18 at 8:30

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.