Como processar caracteres especiais no nome do arquivo usando find

3

Devo encontrar todos os arquivos que começam com determinado caractere, por exemplo

find . -maxdepth 1 \( -name "^m*" -a ! -name "g$" \) -print

mas e se alguém criar um arquivo que tenha caracteres especiais no nome desse arquivo? por exemplo

touch "
marst"

isso não será encontrado, embora atenda aos critérios. Como devo alterar o código para encontrar arquivos pares que começam com um espaço?

Além disso, \( -name "^m*" -a ! -name "g$" \) não funcionará porque os arquivos encontrados não são "marr", mas "./marr", o que significa que isso não encontraria nada. Como alterar o código para coincidir com o início da palavra também?

    
por trolkura 16.12.2015 / 14:19

5 respostas

3

-name sempre corresponde apenas ao nome, ou seja, sem o caminho; e corresponde ao nome whole . Seu valor é um padrão, não uma expressão regular, portanto, nomes de arquivos que começam com m podem ser encontrados com

-name 'm*'

e nomes terminados em g com

-name '*g'

Para usar expressões regulares, consulte a opção -regex .

    
por 16.12.2015 / 14:26
3

Se você deseja corresponder aos nomes de arquivos que começam com m ou seguem um caractere de nova linha, isso seria:

NL='
'
find . \( -name 'm*' -o -name "*${NL}m*" \) -print

Observe que, pelo menos com GNU find , * não corresponderá a uma sequência de bytes que não formam uma sequência de caracteres válida. Você provavelmente seria melhor de usar a localidade C, se isso for um problema em potencial.

LC_ALL=C find . \( -name 'm*' -o -name "*${NL}m*" \) -print

Exemplo:

$ touch mom $'two\nminutes' $'mad\x80'
$ find . -name 'm*'
./mom
$ find . \( -name 'm*' -o -name "*${NL}m*" \) -print
./two?minutes
./mom
$ LC_ALL=C find . \( -name 'm*' -o -name "*${NL}m*" \) -print
./mad?
./two?minutes
./mom

Para nomes de arquivos que têm uma linha que começa com m e não termina com g :

LC_ALL=C find . \( -name 'm*' -o -name "*${NL}m*" \) ! \(
  -name '*g' -o -name "*g${NL}*" \) -print

Algumas implementações de find têm algumas opções fora do padrão para corresponder ao arquivo caminho (geralmente não nome ) usando expressões regulares, mas o comportamento varia entre implementação e são não é necessário aqui.

Em que você precisaria de expressões regulares, seria, por exemplo, encontrar arquivos cujo nome tem linhas que começam com m e nenhuma delas termina em g (como $'cat\nman\ndog' , mas não $'plate\nmug\ncup' nor $'cat\nman\nmug' )

Com o GNU find :

LC_ALL=C find . -regextype posix-extended -regex \
  ".*/(([^m$NL/][^/$NL]*|m[^/$NL]*[^$NL/g]|m|)($NL|\$))*"

Ou arquivos cujo nome tem pelo menos uma linha que começa com m e não termina em g (como $'mad\nmug' mas não $'ming\nmong' ):

LC_ALL=C find . -regextype posix-extended -regex \
  ".*/([^/]*$NL)?m([^$NL/]*[^g$NL/])?(\$|${NL}[^/]*)"
    
por 16.12.2015 / 14:43
0

Você pode usar o sinal -regex para descobrir se precisa de correspondência mais sofisticada que os globs forneçam. Ele combina com o caminho inteiro, então se você quiser combinar apenas a parte do nome do arquivo, você poderia fazer algo como

find . -maxdepth 1 -regex '/[ 
]?m[^/]*[^g]$' -print

Note que por esta resposta você não pode usar \n para corresponder a uma nova linha, então colocamos uma nova linha litteral em nossa classe de personagem com um espaço, desde que você tinha pedido por isso.

    
por 16.12.2015 / 14:31
0

O arquivo criado com ...

touch "
marst"

... não corresponde a nenhum dos dois critérios na questão. Porque não começa com m , começa com uma nova linha. O que você procura pode ser algo assim:

find . -maxdepth 1 -regex ".*/\s*m[^/]*[^g]"

O -regex corresponde ao caminho inteiro do arquivo. .*/ corresponde a qualquer coisa até a última barra, que delimita o arquivo e seu diretório. Agora \s* corresponde a caracteres de espaço em branco (isso pode ser um espaço, nova linha, tabulação); zero ou mais vezes. Depois disso, o m corresponde ao "início" do nome do arquivo (sem espaços em branco, é claro). [^/]* corresponde a qualquer coisa que não seja uma barra. E o% final [^g] corresponde ao último caractere no nome do arquivo, que não deve ser g .

Isso agora corresponderá:

./?marst
./ marst
./  marst
./marst

O ? indica onde a nova linha está.

Notce: Quando você continuar processando essa saída, use o sinal -print0 de find :

find . -maxdepth 1 -regex ".*/\s*m[^/]*[^g]" -print0 | xargs -0 ...

Assim, você pode processar a lista de arquivos ainda mais, mesmo com nomes de arquivos especiais. Delimitará a lista de nomes de arquivos com um nullbyte. O próximo utilitário deve ler a entrada também por delimitado por byte nulo. Por exemplo, xargs com o sinalizador -0 . Claro, isso depende do que você quer fazer com esses arquivos.

    
por 16.12.2015 / 14:34
0

Você não precisa do ^ ou do $ para nomes simples encontrados.
Encontre os padrões para nomes. Um padrão irá:

  • Corresponda o nome inteiro. Do começo ao fim. Sempre.
  • encontre o caminho para qualquer arquivo encontrado antes de usar o padrão.
  • os únicos caracteres especiais são * ? e [ ] (não ^ ou $).

Assim, os arquivos correspondentes que começam com m e não terminam com g :

 find . -maxdepth 1 -name 'm*[!g]' -o -name 'm'

O 'm' abrange o caso em que o arquivo tem apenas um caractere.

No entanto, o arquivo que você criou com touch $'\nmarst' (sim, uma nova linha poderia ser escrita assim no bash) não inicia com m , inicia com uma nova linha $'\n' . Não há como alternar em padrões simples, mas você pode usar a opção OR ( -o ) de find:

find . -maxdepth 1 \( -name 'm*' -o -name $'\n'"m*" \) -a ! -name '*g'

Isso se tornará difícil com exigências mais longas.
Para strings realmente complexas, existe a opção -regex em find.

    
por 17.12.2015 / 00:52