0

I am writing a short script: User chooses a folder with photos and then these photos are sorted by date of modification and renamed with pattern "PhotoX.jpg", where X is is number from 1 to total number of photos. When I execute this script first time everything is fine. The problem is, that when I try to execute this script again, on the same files, then Photo11.jpg disappeas and I don't know why. Here is code:

DIRECTORY=`zenity --file-selection --title="Wybierz miejsce docelowe" --directory`
FILES=()

eval "ls -r -path $DIRECTORY/ -t > temp.txt"
COUNTER=$(wc -l < "temp.txt")
eval mapfile -t FILES < "temp.txt"

eval "cd $DIRECTORY"
COUNTER=$((COUNTER-1))
for ((i=1;i<$COUNTER;i++))
do
    eval "mv ${FILES[$i-1]} Photo$i.jpg"
done

eval "cd"
eval "rm temp.txt"

In all files, exept for Photo11, i get information in terminal, that PhotoX.jpg and PhotoX.jpg are the same files.

7
  • 1
    Why are you using eval so many times? Commented May 28, 2017 at 16:40
  • I am beginner in Bash, my goal is to execute terminal commands Commented May 28, 2017 at 16:42
  • 2
    you can't reliably populate the file-list using eval "ls -r -path $DIRECTORY/ -t > temp.txt". See why [ Parsing ls ] is discouraged. Commented May 28, 2017 at 16:48
  • 1
    ...and btw, if you want to see what's happening at runtime, bash -x yourscript is your friend. Commented May 28, 2017 at 17:06
  • 1
    You can execute a command without eval, just try Commented May 28, 2017 at 17:07

1 Answer 1

3

The code given is broken enough that it's not worth debugging -- better to rewrite.

To robustly prevent the number of output files from being less than the number of input files when input and output names can overlap, it's necessary to split the renaming into two parts: From the original name to a temporary name, and from the temporary name to the output name.

#!/bin/bash

# prompt for a directory via zenity only if not passed in on the environment
[[ -d $directory ]] || {
  directory=$(zenity --file-selection --title="Wybierz miejsce docelowe" --directory)
}

tempnames=( )

# Move files to unique destination names, prefixed with intended final name
counter=1
while IFS= read -r -d ' ' _ && IFS= read -r -d '' filename; do # read time into _
  destname="${directory}/Photo${counter}.jpg"
  [[ $filename = "$destname" ]] && continue  # skip files with correct names already
  tempname=$(mktemp -- "$destname.XXXXXX") || continue # generate a unique dest name
  mv -- "$filename" "$tempname" || {         # and rename to that...
    rm -f -- "$tempname"                     # ...deleting the tempfile and skipping the
    continue                                 #    input file if the rename somehow fails.
  }
  tempnames+=( "$tempname" )                 # ...keeping a log of what it was
  (( counter++ ))                            # finally, increment our counter.
done < <(find "$directory" -type f -printf '%T@ %p\0' | sort -zn)

# Move from temporary names to real ones
for tempname in "${tempnames[@]}"; do
  mv "$tempname" "${tempname%.*}"
done

See BashFAQ #3 to grok how we're combining GNU find and GNU sort to get a stream of files sorted by modification time.

Sign up to request clarification or add additional context in comments.

3 Comments

Even with the temp naming convention, I'd still recommend using mv -i just in case there's a naming conflict somewhere.
@GordonDavisson, can you describe a scenario where that conflict could happen here, without a file being created after the script has already started?
I don't see a way for it to overwrite files (unless, as you said, files are created while it's working); but "I don't see how this can have trouble" doesn't make me feel safe when the failure mode would involve silently and irrevocably deleting files.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.