Count only successful move attempts

Viewed 103

Problem is that I cannot figured out how to count only "successful" attempts of move files.

My script take directories from $PATH as destination and tries to take files from specified directory (until the directory is empty) , shuffle them in random order and move to directories from $PATH.

x=0
while [[ $(ls -A Directory/jpg | wc -l) -gt 0 ]]
do
  for i in "${destination[@]}"
  do
    shuf -zn1 -e Directory/jpg/*.jpg | xargs -0 mv -vt $i/somedir 2>/dev/null
    x=$((x + 1))
  done
done

My "x" counter is actually greater then number of files that were in "Directory/jpg" and I would like to "x" represent only exact numbers of files that were copied.

2 Answers

I modified the script a bit for testing:

#!/bin/bash                                                                                                             
destination[0]=dest1
destination[1]=dest2
destination[2]=dest3
x=0
while [[ $(ls -A source | wc -l) -gt 0 ]]
do
  for i in "${destination[@]}"
  do
    shuf -zn1 -e source/* | xargs -0 mv -vt $i
    x=$((x + 1))
  done
done

echo $x

So with three destination directories and different numbers of files in source, I noticed that x was always a multiple of the number of elements of the destination array.

Removing the 2>/dev/null revealed a hidden problem:

... # other mv output
renamed 'source/file14' -> 'dest2/file14'
renamed 'source/file16' -> 'dest3/file16'
renamed 'source/file5' -> 'dest1/file5'
mv: cannot stat 'source/*': No such file or directory
mv: cannot stat 'source/*': No such file or directory

The last two failed moves were counted!

for i in "${destination[@]}" is always going to loop a number of times equal to the number of elements in the destination array. That is, if destination has three elements, the loop will always execute with i = 1, i = 2, and i = 3, even if there are not that many files. The for loop will always increase x by the number of elements in destination. So, x will be greater than the number of files moved. (unless the number of files is a multiple of the number of elements in the destination array)

This can be fixed by adding a test to the inside of the for loop to check that more files to move exist:

#!/bin/bash                                                                                                             
destination[0]=dest1
destination[1]=dest2
destination[2]=dest3
x=0
while [[ $(ls -A source | wc -l) -gt 0 ]]
do
  for i in "${destination[@]}"
  do
    shuf -zn1 -e source/* | xargs -0 mv -vt $i
    x=$((x + 1))
    [[ $(ls -A source | wc -l) -eq 0 ]] && break # exit the loop if there are no more files to move
  done
done

Consider using && to check the result of the mv command and execute the increment conditionally.

Here's the bash idiom:

command && other-command-if-command-was successful

In the example above, other-command will be executed only if command returned a zero exit status. Here's my edit to your program:

x=0
while [[ $(ls -A Directory/jpg | wc -l) -gt 0 ]]
do
  for i in "${destination[@]}"
  do
    shuf -zn1 -e Directory/jpg/*.jpg | xargs -0 mv -vt $i/somedir 2>/dev/null && x=$((x + 1))
  done
done

The code above probably incorrectly assumes that xargs will return the exit status of mv. I think that it won't. Rather, it's probably best to avoid xargs in this case altogether. After all, shuf is only returning a single entry. Here's a larger modification of the code that will work better:

x=0
while [[ $(ls -A Directory/jpg | wc -l) -gt 0 ]]
do
  for i in "${destination[@]}"
  do
    random_file=$(shuf -zn1 -e Directory/jpg/*.jpg) 
    mv "$random_file" "$i/somedir" && x=$((x + 1))
  done
done
Related