Arquivos Glob * sem * usar um shell

Aug 22 2020

Quero listar os arquivos em um determinado subdiretório, mas estou fazendo isso como parte de um docker execcontêiner do docker, então não quero me incomodar em iniciar um shell de que realmente não preciso. É possível encontrar todas as correspondências para um glob com uma ferramenta de linha de comando simples, e não apenas um shell?

Por exemplo, minha invocação atual é bash -l -c 'echo /usr/local/conda-meta/*.json'. É possível simplificar isso usando uma ferramenta comumente disponível, resultando em algo como globber /usr/local/conda-meta/*.json, que seria muito mais simples e mais leve?

Respostas

16 StéphaneChazelas Aug 23 2020 at 05:12

shé simples e comumente disponível. shé a ferramenta que é chamada para analisar linhas de comando system(cmdline)em vários idiomas. Muitos sistemas operacionais, incluindo alguns GNU, pararam de usar bash(o shell GNU) para implementar shporque ficou muito inchado para fazer apenas aquela coisa simples de analisar linhas de comando e interpretar shscripts POSIX .

Sua bash -l -c 'echo /usr/local/conda-meta/*.json'linha de comando possivelmente já está sendo interpretada por uma shchamada. Então, possivelmente, você pode apenas fazer:

printf '%s\n' /usr/local/conda-meta/*.json

diretamente. Se não:

sh -c 'printf "%s\n" /usr/local/conda-meta/*.json'

Você também pode usar findaqui. findnão faz globbing, mas pode relatar nomes de arquivo que correspondem a padrões semelhantes aos do shell.

LC_ALL=C find /usr/local/conda-meta/. ! -name . -prune -name '*.json'

Ou com algumas findimplementações:

LC_ALL=C find /usr/local/conda-meta -mindepth 1 -maxdepth 1 -name '*.json'

(note que o LC_ALL=Cnecessário aqui para que *corresponda a qualquer sequência de bytes, não apenas aqueles que estão formando caracteres válidos no local atual, é uma construção de shell. Se essa linha de comando não for interpretada por um shell, você pode precisar alterá-la para env LC_ALL=C find...)

Algumas diferenças com shell globs:

  • a lista de arquivos não está classificada
  • arquivos ocultos são incluídos (você pode adicionar um ! -name '.*'para excluí-los)
  • você não obtém saída se não houver um arquivo correspondente. globs têm a característica errada de deixar o padrão como está, não expandido nesse caso.
  • com a primeira variante (padrão), os arquivos serão impressos como /usr/local/conda-meta/./file.json.
  • alguns globs, como x*/y/../*znão são facilmente traduzidos (observe também o comportamento diferente em relação aos links simbólicos para diretórios, nesse caso).

Em qualquer caso, você não pode usar echopara produzir dados arbitrários.

Minha próxima pergunta seria: o que você vai fazer com essa saída? Com echo, você está gerando esses caminhos de arquivo separados por caracteres SPC e com my printfou findacima, delimitados por caracteres NL. Ambos NLe SPCsão caracteres perfeitamente válidos em nomes de arquivo, portanto, essas saídas não são confiáveis ​​pós-processáveis. Você pode usar em '%s\0'vez de '%s\n'(ou usar find's -print0se houver suporte), não adequado para exibição a um usuário, mas pós-processável.

Em termos de eficiência, comparando o Ubuntu 20.04's /bin/sh(traço 0.5.10.2) com o seu find(GNU find4.7.0).

Tempo de inicialização:

$ time (repeat 1000 sh -c '') ( repeat 1000; do; sh -c ''; done; ) 0.91s user 0.66s system 105% cpu 1.483 total $ time (repeat 1000 find . -quit)
( repeat 1000; do; find . -quit; done; )  1.35s user 1.25s system 103% cpu 2.507 total

Globbing alguns jsonarquivos:

$ TIMEFMT='%U user %S system %P cpu %*E total' $ time (repeat 1000 sh -c 'printf "%s\n" /usr/share/iso-codes/json/*.json') > /dev/null
0.95s user 0.72s system 105% cpu 1.587 total
$ time (repeat 1000  find /usr/share/iso-codes/json -mindepth 1 -maxdepth 1 -name '*.json') > /dev/null
1.34s user 1.35s system 103% cpu 2.599 total

Even bashdificilmente é mais lento do que findaqui:

$ time (repeat 1000 bash -c 'printf "%s\n" /usr/share/iso-codes/json/*.json') > /dev/null
1.53s user 1.36s system 102% cpu 2.808 total

É claro que o YMMV depende do sistema, implementação, versão dos respectivos utilitários e das bibliotecas às quais estão vinculados.

Agora, na observação da história, o nome glob na verdade vem do nome de um utilitário chamado globnas primeiras versões do Unix no início dos anos 70. Ele foi localizado /etce invocado por shcomo um ajudante para expandir os padrões de curinga.

Você encontrará alguns projetos online para reviver aquele shell muito antigo, como https://etsh.nl/. Mais como um exercício de arqueologia, você poderia construir o globutilitário a partir daí e ser capaz de fazer:

glob printf '%s\n' '/usr/local/conda-meta/*.json'

Algumas notas de advertência, no entanto.

  • aqueles são globs antigos, [!x](quanto mais [^x]) não é suportado.
  • não é seguro para 8 bits. Na verdade, o 8º bit é usado para escapar dos operadores glob ( $'\xe9*'corresponderia à mesma coisa que i*, $'\xaa*'corresponderia aos nomes de arquivo que começam com *; o shell definiria esse 8º bit para os caracteres citados antes de invocar glob)
  • varia como [a-f]correspondência no valor de byte em vez de ordem de agrupamento (na prática, isso geralmente é uma vantagem IMO).
  • Globs não correspondentes resultam em um No matcherro (novamente, provavelmente de preferência, é algo que foi quebrado pelo shell Bourne no final dos anos 70).

A globfuncionalidade foi posteriormente movida para o shell, começando com o shell PWB e o shell Bourne no final dos anos 70. Posteriormente, algumas funções fnmatch()e glob()foram adicionadas à biblioteca C para permitir que esse recurso seja usado em outros aplicativos, mas não estou ciente de um utilitário padrão nem comum que seja uma interface simples para essa função. Até perlcostumava invocar cshem seus primeiros dias para expandir os padrões glob.

7 BasileStarynkevitch Aug 23 2020 at 14:50

Arquivos Glob sem usar um shell

A documentação óbvia para ler é glob (7) .

Você pode escrever ou usar um programa C chamando fnmatch (3) , glob (3) , nftw (3) , stat (2) , readdir (3)

Se você codificar em Guile , Python , Go , Rust , Ocaml , Common Lisp (por exemplo, SBCL ) ... você encontrará funções semelhantes. Com C ++, olhe para POCO e Qt .

Presumo que você esteja usando um sistema Linux. BTW, meu shell interativo é zsh (cujos recursos de preenchimento automático são IMHO preferíveis).