4

Consider we have many photos with names like DSC_20170506_170809.JPEG. To rename the photos so that they follow the pattern Paris_20170506_170809.JPEG, I've wrote the following script that works perfect.

for file in *.JPEG; do mv ${file} ${file/DSC/Paris}; done

My question is , how we can write this script using a while loop instead of a for loop?

  • 10
    I see no reason whatsoever to convert this into a while loop. – Kusalananda Nov 2 '17 at 10:06
  • 1
    I'm a Bash newbie and try to learn more about bash by practicing. I would be grateful if anyone suggests an alternative using while loop. – sci9 Nov 2 '17 at 10:35
7

There's nothing wrong with using a while loop here. You just have to do it right:

set -- *.jpeg
while (($#)); do
mv -- "${1}" "${1/DSC/Paris}"
shift
done

The while loop above is just as reliable as the for loop (it will work with any file names) and while the latter is - in many instances - the most appropriate tool to use, the former is a valid alternative1 that has its uses (e.g. the above could process three files at a time or process only a certain number of arguments etc).


All these commands (set, while..do..done and shift) are documented in the shell manual and their names are self-explanatory...

set -- *.jpeg
# set the positional arguments, i.e. whatever that *.jpeg glob expands to

while (($#)); do
# execute the 'do...' as long as the 'while condition' returns a zero exit status
# the condition here being (($#)) which is arithmetic evaluation - the return
# status is 0 if the arithmetic value of the expression is non-zero; since $#
# holds the number of positional parameters then 'while (($#)); do' means run the
# commands as long as there are positional parameters (i.e. file names)

mv -- "${1}" "${1/DSC/Paris}"
# this renames the current file in the list

shift
# this actually takes a parameter - if it's missing it defaults to '1' so it's
# the same as writing 'shift 1' - it effectively removes $1 (the first positional
# argument) from the list so $2 becomes $1, $3 becomes $2 and so on...
done

1: It's not an alternative to text-processing tools so NEVER use a while loop to process text.

  • Thank you for answer. Could you please interpret your code line by line to understand what this script does? – sci9 Nov 3 '17 at 4:44
  • 1
    @sci9 - see edit – don_crissti Nov 3 '17 at 14:47
4

for-loops are usually done over static data. That is, data that will not change over the course of the loop, or over a known interval.

while-loops are usually used when it is not known how many iterations will be needed, like prompting a user for input and validating the response until it's correct, or looping over the data read from file or a pipe.

In this case, you are looping over filenames read directly from the current directory, given to the loop by the shell expanding a file globbing pattern. A for loop is the correct looping construct to use. A while loop would only complicate matters and make the code harder to read and to get right.

This would be one example of a while loop doing the same thing as your for loop:

printf '%s\n' DSC*.JPEG |
while read -r name; do
    [ ! -f "Paris${name#DSC}" ] && mv "$name" "Paris${name#DSC}"
done

There are issues here:

  1. The filenames can't contain newlines since the loop reads newline-separated names. We could get around this by getting printf to output \0-separated filenames and read to read these, but that would make it even more complicated (and non-portable).
  2. There is no gain whatsoever. The list of names is still static and we've introduced a redundant step to pass these names from printf to the loop, a step that brought issues with what characters we can allow the filenames to contain (see previous point).

Really, the only thing wrong with your code is that the variable expansions are unquoted.

  • Also note that read would strip the trailing G off the file name if $IFS so happened to contain G. With an unmodified $IFS, you'd have issues with leading and trailing space or tab characters (and newline with NUL delimited data). The syntax to read a line of input verbatim is IFS= read -r line – Stéphane Chazelas Nov 3 '17 at 15:04
  • There's also a missing -- in the OP's code. – Stéphane Chazelas Nov 3 '17 at 15:05
  • Another problem with that while loop is that mv's stdin is redirected to a pipe (which matters in case mv prompts the user). – Stéphane Chazelas Nov 3 '17 at 15:06
3

You can also use an Bash until loop if you wish.

set -- *.jpeg
until ((! $#))
do
    mv -- "${1}" "${1/DSC/Paris}"
    shift
done
  • Cloud anyone kindly interpret this code line by line? – sci9 Nov 3 '17 at 10:27

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.