Shell – How to iterate over two sets of iterables in a shell script

shell-script

Usually when I write a shell script for a specific task, I just make a list of files like so:

#/bin/sh
read -d '' imagefiles <<EOF

./01/IMG0000.jpg
./01/IMG0001.jpg
./01/IMG0002.jpg
./01/IMG0003.jpg
./01/IMG0004.jpg
./01/IMG0005.jpg
./01/IMG0006.jpg
./01/IMG0007.jpg
(a whole bunch of files down to ./10/IMG0102.jpg)

EOF

for i in $imagefiles
  for j in range(len(commands))
do

mv $i ./$j.jpg

  done
done

In this instance I wanted to be able to iterate over the output of seq but following Gilles's suggestion simply wrote that part out of it out as Python (yeah I know that as it is it would execute each command j times, leading to about 100000 executions). Earlier on in the day I was renaming 736 files in sequence, but now I'm renaming 1000-odd files. I'm sure there's a better way to do that (please don't hesitate to tell me), but it would still be nice to know how to iterate over the list of commands and some other iterable.

Best Answer

Ok, so you want to zip two iterables, or in other words you want a single loop, iterating over a bunch of strings, with an additional counter. It's quite easy to implement a counter.

n=0
for x in $commands; do
  mv -- "$x" "$n.jpg"
  n=$(($n+1))
done

Note that this only works if none of the elements that you're iterating over contains any whitespace (nor globbing characters). If you have items separated by newlines, turn off globbing and split only on newlines.

n=0
IFS='
'; set -f
for x in $commands; do
  mv -- "$x" "$n.jpg"
  n=$(($n+1))
done
set +f; unset IFS

If you only need to iterate over the data once, loop around read (see Why is while IFS= read used so often, instead of IFS=; while read..? for more explanations).

n=0
while IFS= read -r x; do
  mv -- "$x" "$n.jpg"
  n=$(($n+1))
done <<EOF
…
EOF

If you're using a shell that has arrays (bash, ksh or zsh), store the elements in an array. In zsh, either run setopt ksh_arrays to number array elements from 0, or adapt the code for array element numbering starting at 1.

commands=(
    ./01/IMG0000.jpg
    …
)
n=0
while [[ $n -lt ${#commands} ]]; do
  mv -- "${commands[$n]}" "$n.jpg"
done
Related Question