0

Previous code:

total=`ls -Rp rootfs | wc -l`
count=0

When I assign a simple addition to a variable:

sudo find rootfs -exec cp -d -- "{}" "/media/$USER/{}" 2> /dev/null \; -exec sync \; -exec count=$((count+1)) \; -exec echo -en "\rcopiati: $count/$total" \;

I get:

find: ‘count=1’: No such file or directory

Also when I exec:

sudo find rootfs -exec cp -d -- "{}" "/media/$USER/{}" 2> /dev/null \; -exec sync \; -exec count=1 \; -exec echo -en "\rcopiati: $count/$total" \;

I get the same error. Why?

For each file copied i want the counter: 1/13444, that is updated to 2/13444, 3/13444, etc...

edit:

I have found a method but it doesn't see hidden files, how can I make them see them in a for loop?

#!/bin/bash
copysync() {
    countfiles() {
        for f in $1/*; do
            if [ -d "$f" ]; then
                countfiles "$f"
            else
                if [ "${f: -2}" != "/*" ]; then
                    total=$((total+1))
                fi
            fi
        done
    }
    recursivecp() {
        for f in $1/*; do
            if [ -d "$f" ]; then
                mkdir -p "/media/$USER/$f"
                recursivecp "$f"
            else
                if [ "${f: -2}" != "/*" ]; then
                    sudo cp -a "$f" "/media/$USER/$f"
                    sudo sync
                    count=$((count+1))
                    echo -en "\rCopied: $((count*100/total))%"
                fi
            fi
        done
    }
    total=0
    countfiles $1
    count=0
    recursivecp $1
}
copysync rootfs

2 Answers2

5

The shell expands count=$((count+1)) before running find.

Then find will try to execute the argument to -exec as a command. This must be a program or script, it cannot be a shell builtin or shell syntax for variable assignment.

Counting the files found does not work this way because find starts a new process for -exec, so the result of a variable assignment would not be available in the parent shell.

I suggest to print a line for every file found and pipe the output of find to wc -l, for example

find rootfs -exec cp -d -- "{}" "/media/$USER/{}" \; -exec sync \; -print|wc -l

To get some output while copying the files you can use something like this:

find rootfs|while IFS= read -r file
do
    cp -d -- "$file" "/media/$USER/$file"
    sync
    count=$((count+1))
    echo -en "\rcopiati: $count/$total"
done

Remarks:

This does not work for file names that contain a newline (and maybe other special characters).

The script may not work if rootfs contains subdirectories. You should either handle this case or use find's options -maxdepth and -type f to avoid this problem.

Bodo
  • 6,068
  • 17
  • 27
2

It seems as if you're trying to execute each and every command with -exec. This would not work in the general case as -exec only executes external commands.

Instead, call a single in-line script and let find act as a generator for a loop in that script:

find rootfs -type f -exec sh -c '
    for pathname do
        cp -d "$pathname" "/media/$USER" &&
        echo . &&
        sync
    done' sh {} + | wc -l

This would find all regular files in or below the rootfs directory. For batches of these files, a short inline sh -c script is called. This script copies each file to the given directory, outputs a dot followed by a newline for each successfully copied file, and calls sync.

The wc -l counts the number of dots outputted and reports this count. We don't count the pathnames themselves as this count would be misleading if any pathname contains an embedded newline character.

Without using find, this can be done in e.g. bash like so:

shopt -s globstar dotglob nullglob

for pathname in rootfs/**/*; do
    [[ ! -f $pathname ]] && continue
    cp -d "$pathname" "/media/$USER" &&
    echo . &&
    sync
done | wc -l

This uses a globbing pattern containing the ** glob which matches down inte subdirectories if the globstar shell option is set. I also set dotglob to be able to see hidden names, and the nullglob shell option to avoid running the loop at all if the pattern doesn't match anything.

The same thing, but with a counter:

shopt -s globstar dotglob nullglob

count=0
for pathname in rootfs/**/*; do
    [[ ! -f $pathname ]] && continue
    cp -d "$pathname" "/media/$USER" &&
    count=$(( count + 1 ))
    sync
done
printf 'count=%d\n' "$count"
Kusalananda
  • 333,661