Como selecionar um intervalo de arquivos por nome parcial

Como selecionar um intervalo de arquivos por nome parcial

Quero selecionar um intervalo de arquivos usando parte de seu nome para ser usado na cópia, movimentação, etc.

Lista de exemplos aleatórios:

  1. alfa1a
  2. alfa2aa
  3. alfa3abc
  4. bravoA1
  5. bravoB2bb
  6. Charlie
  7. deltaA1fdfd
  8. deltaA2dafa
  9. deltaA2222
  10. deltaC1
  11. ... (vários outros arquivos)

Gostaria de executar algum comando que vá de alpha2* a deltaA*, que selecionaria 2 a 9 na lista. Dessa forma, posso selecionar um intervalo com base no nome, sem me preocupar com quantos arquivos realmente existem e sem obter extras.

Responder1

start=alpha2*
end=deltaA*

for file in *
do
    if [[ $file > $start && ( $file < $end || $file == $end ) ]]; then
        echo $file
    fi
done

Em vez de echo, você pode armazenar os arquivos em um array:

array+=($file)

E então use o array para copiar, mover os arquivos... ou simplesmente executar o comando dentro do loop for.

Responder2

A programação Shell não é muito boa em comparações lexicográficas. No bash, ksh ou zsh, você pode usar o< operador condicionalpara comparar duas strings (POSIX sh possui apenas comparações numéricas).

run_in_range () {
  start_at="$1"; end_before="$2"; command="$3"; shift 3
  for item do
    if [[ ! ($x < $start_at) && ($x < $end_before) ]]; then
      "$command" "$item"
    fi
  done
}
run_in_range alpha2 deltaB some_command *

Observe que usei o primeiro item excluído como segundo argumento: deltaBé a menor string que vem depois de todas as strings que começam com deltaA. Se quiser passar deltaAcomo argumento, você pode fazer desta forma.

run_in_inclusive_range () {
  start_at="$1"; end_on_prefix="$2"; command="$3"; shift 3
  for item do
    if [[ ! ($x < $start_at) && ($x < $end_on_prefix || $x == "$end_on_prefix"*) ]]; then
      "$command" "$item"
    fi
  done
}
run_in_inclusive_range alpha2 deltaA some_command *

Este código não depende da ordem do curinga: funciona mesmo que a lista de itens não esteja ordenada. Isso torna o código mais robusto, mas um pouco mais lento, pois precisa percorrer toda a lista.

Aqui está uma abordagem alternativa que depende apenas dos recursos POSIX. Ele usa sortpara fazer comparações de strings. Este assume que não há novas linhas nos itens. Esta função restaura a configuração padrão para IFSe globbing (é possível, mas irritantemente difícil, restaurar a configuração ambiente).

run_in_range () {
  IFS='
'; set -f
  start_at="$1"; end_before="$2"; command="$3"; shift 3
  set $({
          printf '%s1\n' "$@"
          printf '%s0\n' "$start_at" "$end_before"
        } | sort | sed '/0$/,/0$/ s/1$//p')
  unset IFS; set +f
  for item do
    "$command" "$item"
  done
}

Explicação:

  1. Crie uma lista contendo os itens 1anexados e os limites 0anexados.
  2. Classifique a lista.
  3. Imprima as linhas entre as duas linhas de limite (as linhas que terminam em 0), mas apenas as linhas que terminam em 1(ou seja, os itens) e sem o final 1.

Responder3

Parece-me que o problema é mais simples de resolver do que parece:

Vamos supor que exista uma maneira de obter a lista, um arquivo por linha.
Para o diretório atual:

list="$(printf '%s\n' *)"

Basta anexar os limites à lista e classificá-la:

nl='
'
lim1="alpha2"
lim2="deltaA"

echo -- "$list""$nl""$lim1""$nl""$lim2" | sort

Em seguida, comece a imprimir em lim1 (tomando cuidado para que lim1 e lim2 não correspondam a um nome de arquivo existente) e pare em lim2.

Além disso, para simplificar, selecione a string mais curta que está logo antes do arquivo que você deseja imprimir e também a string mais curta que está logo após o último arquivo que precisa ser selecionado.

O que é útil é uma lista exclusiva (que não inclua os limites),
mas ambas as soluções são fornecidas.

Valores básicos:

list="$(printf '%s\n' *)"

lim1=alpha2
lim2=deltaA

Uma solução com awk para uma lista inclusiva:

echo "awk----------------inclusive"
echo "$list""$nl""$lim1""$nl""$lim2" | sort |
    awk -v lim1="$lim1" -v lim2="$lim2" '$0==lim1,$0==lim2{print}'

Uma solução com awk para uma lista exclusiva:

echo "awk----------------exclusive"
echo "$list""$nl""$lim1""$nl""$lim2" | sort |
    awk -v lim1="$lim1" -v lim2="$lim2" '$0==lim1{p=1;next};$0==lim2{p=0};p'

Uma solução com shell para uma lista inclusiva:

echo "shell---------------inclusive"
show=0
echo "$list""$nl""$lim1""$nl""$lim2" | sort |
    while IFS="$nl" read -r line; do
        [ "$line" = "$lim1" ] && show=1
        [ "$show" = "1"     ] && printf '%s\n' "$line"
        [ "$line" = "$lim2" ] && show=0
    done

E uma solução exclusiva para o shell:

show=0
echo "$list""$nl""$lim1""$nl""$lim2" | sort |
    while read -r line; do
        [ "$line" = "$lim2" ] && show=0
        [ "$show" = "1"     ] && printf '%s\n' "$line"
        [ "$line" = "$lim1" ] && show=1
    done

informação relacionada