Como iterar dois conjuntos de iteráveis em um script de shell?

3

Normalmente, quando escrevo um script de shell para uma tarefa específica, apenas faço uma lista de arquivos assim:

#/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

Neste caso, eu queria ser capaz de iterar a saída de seq , mas seguindo a sugestão de Gilles, simplesmente escrevi essa parte como Python (sim, eu sei que, como é, executaria cada comando j vezes, levando a cerca de 100.000 execuções). Mais cedo no dia em que eu estava renomeando 736 arquivos em seqüência, mas agora estou renomeando arquivos 1000-ímpares. Tenho certeza de que há uma maneira melhor de fazer isso (por favor, não hesite em me dizer), mas ainda seria bom saber como fazer uma iteração na lista de comandos e em alguns outros iteráveis.

    
por ixtmixilix 23.08.2011 / 15:57

4 respostas

5

Ok, você quer zipar dois iteráveis, ou em outras palavras, você quer um único loop, iterando sobre um monte de strings, com um contador adicional. É muito fácil implementar um contador.

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

Observe que isso só funciona se nenhum dos elementos sobre os quais você está interagindo contiver espaços em branco (nem caracteres globbing). Se você tiver itens separados por novas linhas, desative globbing e divida somente em novas linhas.

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

Se você precisar apenas iterar os dados uma vez, faça um loop em torno de read (consulte Por que while IFS= read é usado com tanta frequência, em vez de IFS=; while read.. ? para mais explicações).

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

Se você estiver usando um shell que tenha matrizes (bash, ksh ou zsh), armazene os elementos em uma matriz. Em zsh, execute setopt ksh_arrays para numerar elementos de matriz de 0 ou adapte o código para a numeração de elemento de matriz a partir de 1.

commands=(
    ./01/IMG0000.jpg
    …
)
n=0
while [[ $n -lt ${#commands} ]]; do
  mv -- "${commands[$n]}" "$n.jpg"
done
    
por 24.08.2011 / 00:39
3

Se o seu shell for bash , não será necessário executar o programa seq . É só usar

for i in {1..736}
  do

Se você quiser uma seqüência com zeros à esquerda, use apenas {001..736} (ou mesmo {0001..0736} ).

Além disso, quando possível, tente não usar backticks ( veja este pergunta de como isso pode atrapalhar as coisas ).

Outra coisa é, quando você quer operar em um conjunto de arquivos, você pode achar isto útil:

for file in /some/path/*
  do

Que funcionará em todos os arquivos (e pastas) no diretório /some/path/ . Para restringir isso apenas aos arquivos, você pode usar um teste como [ -f "$file" ] , por exemplo

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

Além disso, a construção /some/path/* pode ser ajustada por padrões de shell como /some/path[123]/* , /some/path[A-Z]/b*[e-g]* , /some/path{1,5,23}/* etc.

No que diz respeito à primeira parte da sua pergunta, você pode simplesmente usar um loop aninhado for (ou while ou qualquer) assim:

for cm in $commands; do
 for file in /some/path[ab]{1,4,2}/*.log; do
    mv "${file}" "${file//text/replacement}"
 done
done
    
por 23.08.2011 / 19:51
3

Eu não acho que você pode repetir várias coisas em paralelo no bash.

Eu começaria com i=1 . em seguida, itere a sua lista de arquivos e inclua let i=$i+1 no loop. Também inclua um teste para sair do loop se $i -ge 736 se você quiser sair da iteração sobre qualquer entrada após esse número de loops.

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

Se você estivesse tentando bastante depois do X número de arquivos, você poderia usar um teste como este:

[[ $i -ge 736 ]] && return || let i=$i+1
    
por 23.08.2011 / 16:39
-4

o bash suporta matrizes:

link

OR

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

Isso renomeará os arquivos a.txt b.txt c.txt para 1-a.txt 2-b.txt 3-c.txt.

    
por 23.08.2011 / 16:44