Encontre arquivos com extensões dadas em argumentos usando regex

1

Digamos que você tenha um comando a seguir

search /home/user proc .h .c .txt ...

Estou construindo um script com o comando find para obter todos os arquivos que começam com o nome e terminam com uma das extensões.

Eu consegui construir isso usando um loop:

directory=$1
fileName=$2
fileExtensions=""

for arg in "$@"
do
    #skipping first and second argument
     if [ $arg = $1 -o $arg = $2 ]; then 
        continue;
    fi
    fileExtensions+="${arg//.}\|"
done
#removing the last '|', otherwise regex parser error occurs
fileExtensions=${fileExtensions::-1}

find $directory -name "$fileName*" -regex ".*\.\($fileExtensions)"

Existe uma maneira mais elegante de conseguir isso usando o regex?

Obrigado pela sua ajuda!

    
por Dandry 18.10.2016 / 19:10

2 respostas

1

O script pode ser reduzido para:

directory=$1
fileName=$2
shift 2

a="$*"    b="${*#.}"
(( ${#a} - ${#b} - $# )) && echo "some extension(s) is(are) missing a leading dot." >&2

fileExtensions="$(IFS=\|; echo "${*#.}")"

find "$directory" -name "$fileName*" -regextype posix-egrep -regex ".*\.($fileExtensions)$"

Por padrão, find aceita regex do tipo emacs, para usar esse tipo várias barras invertidas são necessárias, como \| . Isso poderia ser evitado usando um tipo diferente de regex (como acima).

Em que ${*#.} remove o ponto inicial (se existir) e une todos os "Parâmetros posicionais" restantes com o valor do primeiro caractere do IFS, que foi definido como | para a execução da subshell.

Apenas uma atribuição de variável com uma "Expansão de Parâmetro" é tudo o que é necessário.

EDITAR
O (( ${#a} - ${#b} - $# )) é usado para verificar se todas as extensões fornecidas na lista de argumentos começam com um ponto.

A contagem de caracteres ( ${#a} ) de todos os argumentos concatenados ( a=$* )
deve ser igual a um a contagem de caracteres ( ${#b} ) de todos os argumentos concatenados (um ponto principal removido) ( b=${*#.} )
mais e mais o número de argumentos ( $# ).

${#a} == ${#b} + $#

Se e somente se todos os argumentos tiverem um ponto inicial.

Como teste aritmético:

((  ${#a} - (${#b} + $#)  ))

Ou também:

((  ${#a} - ${#b} - $#  )) && echo "missing leading dot(s)."

Editar II

A lista de extensões dadas nos argumentos da linha de comando é processada aqui:

fileExtensions="$(IFS=\|; echo "${*#.}")"

Veja como funciona, de dentro para fora:

$*   # This generates a string of all positional arguments using IFS.

De LESS=+'/Special Parameters' man bash :

That is, "$*" is equivalent to "$1c$2c...", where c is the first character of the value of the IFS variable.

Em seguida, usamos "expansão de parâmetro" para cortar a frente de cada argumento posicional:

${parameter#word}     # used as ${*# } above.

De LESS=+/'parameter#word' man bash :

If parameter is @ or *, the pattern removal operation is applied to each positional parameter in turn, and the expansion is the resultant list.

O word na expansão anterior está definido para um ponto . , removendo assim os pontos na frente de todos os parâmetros posicionais.

Como o IFS nesse ponto começa com um caractere | , isso é usado para criar uma string com | como o separador para a lista de parâmetros que foi distribuída de um ponto na frente.

Essa string é fornecida ao comando echo para que seja impressa.
Mas antes que o comando echo seja executado, a variável $IFS é definida como | .

Isso é empacotado dentro de uma execução de comando $(…) (para criar um sub shell que irá esquecer a mudança para o IFS quando ele terminar).

Em seguida, atribuímos a string a uma variável:

fileExtensions="$(IFS=\|; echo "${*#.}")"

Em resumo: isso transforma .c .h .txt em c|h|txt .

    
por 19.10.2016 / 09:30
1

Parece que o regex deve funcionar, e a única outra opção que eu posso fazer é algo como -name "proc*" \( -name "*.c" -o -name "*.h" ... \) o que provavelmente não é muito mais simples.

Algumas pequenas coisas que podem ser feitas:

1) Você pode usar shift para se livrar de $1 e $2 , em vez do condicional dentro do loop.

2) Você pode combinar o -name e o -regex , para que, no final, você tenha -regex ".*/proc.*\.(c|h|txt)"

3) Se você quiser tornar o código mais curto, você pode unir os parâmetros posicionais com IFS e $* :

$ set .c .h
$ IFS='|'
$ echo "(${*/./})"
(c|h)

(não estou dizendo nada sobre legibilidade ou elegância).

    
por 18.10.2016 / 20:07