Estou tentando invocar um script com uma lista de nomes de arquivos coletados por find
. Nada de especial, apenas algo assim:
$ myscript `find . -name something.txt`
O problema é que alguns dos nomes de caminho contêm espaços e, portanto, são divididos em dois nomes inválidos na expansão do argumento. Normalmente, eu colocaria os nomes entre aspas, mas aqui eles são inseridos pela expansão de aspas. Eu tentei filtrar a saída find
e cercar cada nome de arquivo com aspas, mas quando o bash os vê, é tarde demais para removê-los e eles são tratados como parte do nome do arquivo:
$ myscript `find . -name something.txt | sed 's/.*/"&"/'`
No such file or directory: '"./somedir/something.txt"'
Sim, essas são as regras de como a linha de comando é processada, mas como posso contorná-la?
Isso é embaraçoso, mas não estou conseguindo apresentar a abordagem correta. Finalmente descobri como fazê-lo xargs -0 -n 10000
... mas é um truque tão feio que ainda quero perguntar: como cito os resultados da expansão de cotações anteriores ou obtenho o mesmo efeito de outra maneira?
Edit: Eu estava confuso sobre o fato que xargs
faz coleta todos os argumentos em uma única lista de argumentos, a menos que seja dito de outra forma ou limites do sistema pode ser excedida. Obrigado a todos por me esclarecer! Outros, lembre-se disso ao ler a resposta aceita, porque ela não é apontada diretamente.
Aceitei a resposta, mas minha pergunta permanece: não existe uma maneira de proteger espaços na $(...)
expansão de backtick (ou )? (Observe que a solução aceita é uma resposta não-bash).
IFS="
newline"
). Mas é necessário executar o script em todos os nomes de arquivos? Caso contrário, considere usar-se para executar o script para cada arquivo.Respostas:
Você pode fazer o seguinte usando algumas implementações
find
exargs
assim.ou, normalmente, apenas
find
:Exemplo
Digamos que eu tenha o seguinte diretório de exemplo.
Agora, digamos que tenho isso para
./myscript
.Agora, quando eu executo o seguinte comando.
Ou quando eu uso o segundo formulário da seguinte forma:
Detalhes
find + xargs
Os 2 métodos acima, embora pareçam diferentes, são essencialmente os mesmos. O primeiro é pegar a saída de find, dividindo-a usando NULLs (
\0
) através do-print0
switch para encontrar. Oxargs -0
foi projetado especificamente para receber entradas divididas usando NULLs. Essa sintaxe não-padrão foi introduzida pelo GNUfind
exargs
também é encontrada atualmente em alguns outros, como os BSDs mais recentes. A-r
opção é necessária para evitar chamadasmyscript
sefind
não encontrar nada no GNU,find
mas não nos BSDs.NOTA: Toda essa abordagem depende do fato de você nunca passar uma string excessivamente longa. Se for, uma segunda invocação de
./myscript
será iniciada com o restante dos resultados subsequentes da localização.encontre com +
Essa é a maneira padrão (embora tenha sido adicionada apenas relativamente recentemente (2005) à implementação do GNU
find
). A capacidade de fazer o que estamos fazendoxargs
é literalmente incorporadafind
. Assim,find
você encontrará uma lista de arquivos e passará a lista o maior número possível de argumentos para o comando especificado depois-exec
(observe que{}
só pode ser a última+
neste momento), executando os comandos várias vezes, se necessário.Por que não citar?
No primeiro exemplo, estamos pegando um atalho, evitando completamente os problemas com a citação, usando NULLs para separar os argumentos. Quando
xargs
é fornecida essa lista, ela é instruída a dividir os NULLs, protegendo efetivamente nossos átomos de comando individuais.No segundo exemplo, mantemos os resultados internos
find
e, portanto, ele sabe o que é cada átomo de arquivo e garantirá o tratamento adequado deles, evitando assim o negócio de citá-los.Tamanho máximo da linha de comando?
Essa pergunta surge de tempos em tempos, então, como bônus, eu a adiciono a essa resposta, principalmente para que eu possa encontrá-la no futuro. Você pode usar
xargs
para ver como é o limite do ambiente:fonte
+
argumentofind
(e você também usa+
prosa, então perdi sua explicação na primeira vez). Mas mais ao ponto, eu tinha entendido mal o quexargs
faz por padrão !!! Em três décadas de usar Unix Eu nunca tive um uso para ele até agora, mas eu pensei que eu sabia que a minha caixa de ferramentas ...xargs
é um diabo de um comando. Você precisa lê-lo efind
as páginas de manual muitas vezes para entender o que eles podem fazer. Muitas das opções são contra-positivas uma da outra, o que aumenta a confusão.$(..)
agora. Ele lida automaticamente com o aninhamento de cotações etc. Os backticks estão sendo descontinuados.No exemplo acima,
find
localiza todos os nomes de arquivos correspondentes e os fornece como argumentos paramyscript
. Isso funciona com nomes de arquivos, independentemente de espaços ou qualquer outro caractere ímpar.Se todos os nomes de arquivos couberem em uma linha, o myscript será executado uma vez. Se a lista for muito longa para o shell manipular, o find irá executar o myscript várias vezes, conforme necessário.
MAIS: Quantos arquivos cabem em uma linha de comando?
man find
diz quefind
constrói linhas de comando "da mesma maneira que o xargs constrói suas". Eman xargs
que os limites dependem do sistema e que você pode determiná-los executandoxargs --show-limits
. (getconf ARG_MAX
também é uma possibilidade). No Linux, o limite é tipicamente (mas nem sempre) em torno de 2 milhões de caracteres por linha de comando.fonte
Mais um acréscimo à excelente resposta de @ slm.
A limitação no tamanho dos argumentos está na
execve(2)
chamada do sistema (na verdade, está no tamanho cumulativo das seqüências de caracteres do argumento e do ambiente e ponteiros). Semyscript
estiver escrito em uma linguagem que seu shell possa interpretar, talvez você não precise executá- lo; você pode fazer com que seu shell o interprete sem ter que executar outro intérprete.Se você executar o script como:
É como:
Exceto que ele está sendo interpretado por um filho do shell atual, em vez de executá- lo (o que eventualmente envolve a execução
sh
(ou o que a linha she-bang especificar, se houver) com ainda mais argumentos).Agora, obviamente, você não pode usar
find -exec {} +
com o.
comando, como.
sendo um comando interno do shell, ele deve ser executado pelo shell, não porfind
.Com
zsh
, é fácil:Ou:
Embora com
zsh
, você não precisariafind
em primeiro lugar, pois a maioria de seus recursos é incorporada aozsh
globbing.bash
variáveis, no entanto, não podem conter caracteres NUL; portanto, você precisa encontrar outra maneira. Uma maneira poderia ser:Você também pode usar globbing recursivo no estilo zsh com a
globstar
opçãobash
4.0 e posterior:Observe que os
**
links simbólicos foram seguidos até os diretórios até serem corrigidos nobash
4.3. Observe também quebash
não implementazsh
qualificadores de globbing para que você não obtenha todos os recursosfind
.Outra alternativa seria usar o GNU
ls
:Os métodos acima também podem ser usados se você quiser ter certeza de que
myscript
é executado apenas uma vez (falhando se a lista de argumentos for muito grande). Nas versões recentes do Linux, você pode aumentar e até elevar essa limitação na lista de argumentos com:(Tamanho da pilha de 1GiB, um quarto do qual pode ser usado para a lista arg + env).
(sem limite)
fonte
Na maioria dos sistemas, há um limite no comprimento de uma linha de comando passada para qualquer programa, usando
xargs
ou-exec command {} +
. Deman find
:As invocações serão muito menores, mas não garantidas. O que você deve fazer é ler a NUL separados nomes no script de stdin, possível com base em um argumento de linha de comando
-o -
. Eu faria algo como:e implemente os argumentos das opções de
myscript
acordo.fonte
xargs
funciona. Sua solução é realmente a mais robusta, mas é um exagero nesse caso.Não, não existe. Por que é que?
Bash não tem como saber o que deve ser protegido e o que não deve.
Não há matrizes no arquivo / canal unix. É apenas um fluxo de bytes. O comando dentro do
``
ou$()
gera um fluxo, que bash engole e trata como uma única sequência. Nesse ponto, você só tem duas opções: colocá-lo entre aspas, mantê-lo como uma sequência ou nu, para que o bash o divida de acordo com o comportamento configurado.Então, o que você deve fazer se quiser uma matriz é definir um formato de bytes que tenha uma matriz, e é isso que as ferramentas gostam
xargs
efind
fazem: se você as executa com o-0
argumento, elas funcionam de acordo com um formato de matriz binária que termina os elementos com o byte nulo, adicionando semântica ao fluxo de bytes opaco.Infelizmente,
bash
não pode ser configurado para dividir seqüências de caracteres no byte nulo. Agradecemos a /unix//a/110108/17980 por nos mostrar o quezsh
pode.xargs
Você deseja que seu comando seja executado uma vez e disse que
xargs -0 -n 10000
resolve o seu problema. Não, garante que, se você tiver mais de 10000 parâmetros, seu comando será executado mais de uma vez.Se você deseja executá-lo estritamente uma vez ou falhar, é necessário fornecer o
-x
argumento e um-n
argumento maior que o-s
argumento (realmente: grande o suficiente para que um monte de argumentos de comprimento zero mais o nome do comando não se encaixem o-s
tamanho). ( homem xargs , veja trecho bem abaixo)O sistema em que estou atualmente tem uma pilha limitada a cerca de 8 milhões, então aqui está o meu limite:
bater
Se você não deseja envolver um comando externo, o loop while-read que alimenta uma matriz, conforme mostrado em /unix//a/110108/17980 , é a única maneira de o bash dividir as coisas em o byte nulo.
A ideia de criar o script
( . ... "$@" )
para evitar o limite de tamanho da pilha é legal (tentei, funciona!), Mas provavelmente não é importante para situações normais.Usar um fd especial para o pipe de processo é importante se você quiser ler algo mais do stdin, mas, caso contrário, não precisará dele.
Portanto, a maneira "nativa" mais simples, para as necessidades domésticas diárias:
Se você deseja que sua árvore de processos seja limpa e agradável de ver, esse método permite
exec mynonscript "${files[@]}"
, o que remove o processo bash da memória, substituindo-o pelo comando chamado.xargs
sempre permanecerá na memória enquanto o comando chamado é executado, mesmo se o comando for executado apenas uma vez.O que fala contra o método bash nativo é o seguinte:
O bash não é otimizado para manipulação de array.
homem xargs :
fonte
ls "what is this"
vs.ls `echo '"what is this"'`
. Alguém esqueceu de implementar o processamento de cotações para o resultado de aspas.$(...)
expansão de backtick (ou )?", Portanto, parece apropriado ignorar o processamento que não é feito nessa situação.bash
não o apóie nativamente como aparentemente o apóiazsh
.printf "%s\0"
e resolvixargs -0
uma situação de cotação em que uma ferramenta intermediária passava parâmetros através de uma string analisada por um shell. A citação sempre volta para te morder.