Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

share|improve this question
    
Why do you need seq.. can't you just increment a number each time you process a file name? –  Peter.O Aug 23 '11 at 14:39
    
Your example code iterating over a list of commands is incomplete and I'm not quite sure I see the purpose or now it is supposed to interact with your sequence. Could you be more specific? –  Caleb Aug 23 '11 at 14:56
    
ixtmixilix, when you ask a question about shell scripting, it is very helpful to indicate what shell you are using (use echo $SHELL for example if you are not sure). Or otherwise, say that you are interested in a portable solution. –  rozcietrzewiacz Aug 23 '11 at 20:09
    
Done. (See updated answer). –  rozcietrzewiacz Aug 23 '11 at 21:35
    
I don't understand what you're trying to do. What does “iterate over two sets of iterables” mean? Explain what you want the script to do. In a pinch, even Python code would be better than nothing. –  Gilles Aug 23 '11 at 21:40
show 5 more comments

4 Answers

up vote 5 down vote accepted

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
share|improve this answer
add comment

If your shell is bash, you don't need to run the seq program. Just use

for i in {1..736}
  do

If you want a sequence with leading zeros, just use {001..736} (or even {0001..0736}) instead.

Also, when possible, try not to use backticks (see this question for how it can mess things up).

Another thing is, when you want to operate on a set of files you might find this useful:

for file in /some/path/*
  do

Which will operate on all the files (and folders) in the directory /some/path/. To narrow this down to files only, you can use a test like [ -f "$file" ], for example

for file in /some/path/*
  do
  [ -f "$file" ] || continue
  (do your stuff here)

Furthermore, the construct /some/path/* can be tuned by shell patterns like /some/path[123]/*, /some/path[A-Z]/b*[e-g]*, /some/path{1,5,23}/* etc.

As far as the first part of your question is concerned, you can just use a nested for (or while or any) loop like this:

for cm in $commands; do
 for file in /some/path[ab]{1,4,2}/*.log; do
    mv "${file}" "${file//text/replacement}"
 done
done
share|improve this answer
add comment

I don't think you can iterate over multiple things in parallel in bash.

I would start out i=1. then iterate over your list of files and include let i=$i+1 in the loop. Also include a test to break out of the loop if $i -ge 736 if you want to quit iterating over whatever your input is after that number of loops.

i=0
for file in ./{01..10}/*jpg; do
    let i=$i+1
    mv "$file" "$i-$file"
done

If you were trying to quite after X number of files you could use a test like this:

[[ $i -ge 736 ]] && return || let i=$i+1
share|improve this answer
add comment

bash supports arrays:

http://tldp.org/LDP/abs/html/arrays.html

OR

COUNT=0
for i in `ls *.txt`
do
  COUNT=`expr $COUNT + 1`
  mv "$i" "$COUNT-$i"
done

This would rename files a.txt b.txt c.txt to 1-a.txt 2-b.txt 3-c.txt.

share|improve this answer
    
Can you give a demonstration of how arrays could make this particularly scripting issue easier / faster / more elegant? –  Caleb Aug 23 '11 at 14:56
    
Arrays would answer the question of "How to iterate over two sets of iterables in a shell script?" (which kind of differed from the explanation of the problem). if you increment COUNT, then you could manage ${ARRAY1[$COUNT]} ${ARRAY2[$COUNT]} and ${ARRAY3[$COUNT]} in the same seq 1 10 loop for instance. For the explanation of the problem, I would use the "for i in ls" solution. –  aspitzer Aug 23 '11 at 15:07
4  
Don't parse the output of ls like that! The shell globing will handle that correctly, using ls will break it! –  Caleb Aug 23 '11 at 15:16
1  
If you're using bash, this approach to incrementing a variable can be done within the shell: (( COUNT++ )) –  glenn jackman Aug 23 '11 at 15:46
1  
No, Caleb is right. Don't parse the output of ls. It doesn't work. Also, unless you're running on an antique, use shell arithmetic, you don't need expr any more. –  Gilles Aug 23 '11 at 22:29
show 2 more comments

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.