Manter nomes de arquivos como argumentos separados em comandos sucessivos

4

Um aviso: Este não é relacionado a um comando específico como ls ou grep, embora eu os use nos exemplos, isso é mais uma coisa bacana.

Digamos que eu queira ver os tamanhos de arquivo, registros de data e hora / owner / group em um conjunto de arquivos (ls -l) que contenham "uma determinada palavra". Normalmente eu faria isso:

ls -l $( grep -l 'a certain word' * )

E isso funciona como mágica até que qualquer um dos nomes de arquivos contenha um espaço, como um arquivo chamado "file name". Então, eu não consigo encontrar o que acha que são dois nomes de arquivo, "arquivo" e "nome".

Os scripts bash têm uma maneira familiar de resolver uma variante desse problema, se você passar nomes de arquivos na linha de comando para um script, eles serão referenciados como $ {1}, $ {2}, etc. envolvê-los entre aspas, ou seja, "$ {1}", "$ {2}", então se eles tiverem espaços você não tem um problema e você pode até mesmo processar todos os nomes dos arquivos de uma só vez usando "$ {@}", que irá listar corretamente cada um dos parâmetros, e mantê-los intactos, mesmo se eles tiverem espaços - Eu só não encontrei um mecanismo para o problema de estilo grep / ls que eu listei acima. Se você quebrar o comando de expansão, $( ) com aspas, ele tratará todos os arquivos como um grande parâmetro para o ls.

Eu geralmente trabalhei com algo como grep -l .... | while read file; do ls -l "${file}"; done , mas eu realmente não deveria ter que fazer isso. E fazer isso ainda não permite coisas como classificar os arquivos por data / tamanho / etc (por exemplo, ls -ltr $( grep -l ....) )

Eu continuo pensando que o bash tem uma solução para isso e eu ainda não encontrei isso ainda e esse problema só aparece de vez em quando e eu costumo trabalhar em torno dele, mas agora está me incomodando o suficiente eu acho que deve haver um maneira.

E sim, eu vi o seguinte, eles são questões resolvidas pela ferramenta específica que está sendo usada (como tar), ou as respostas são "desculpe, você não pode chegar lá a partir daqui" porque a pergunta é comando específico. Eu estou pedindo para lidar com qualquer nome de arquivo inserido no espaço dado como resultado para ser passado como parâmetros para outro comando, não mais tempo ou encontrar magia para resolver este problema. Eu preferiria que essa pergunta ficasse sem resposta (uma prova de que não há uma solução) em vez de ter qualquer uma das perguntas não-a-mesma-coisa oferecidas:

por Dev Null 01.08.2016 / 22:58

2 respostas

4

Hmm. @don_crissti já deu a resposta para grep em um comentário. Mas como você disse que não era realmente sobre grep ou ls , vou reescrever o comando em questão para não usar esses comandos.

O que eu acho que você quer é:

do-something-with $(produce-list-of-files)

onde a saída de um comando deve ser descartada como parâmetros da linha de comando para outro comando. Apenas acontece de ser um utilitário apenas para isso, é chamado xargs (man page) .

Se os nomes dos arquivos forem "legais", poderíamos fazer apenas

produce-list-of-files | xargs do-something-with

Se os nomes dos arquivos puderem conter espaços, mas forem separados por novas linhas, precisamos informar xargs para não dividir em qualquer espaço em branco, mas somente novas linhas:

produce-list-of-files | xargs -d '\n' do-something-with

Se os nomes dos arquivos também puderem conter novas linhas, a lista deve ser separada por NULs ('\ 0', byte com valor zero) e precisamos de um xargs que ofereça suporte a isso. Pelo menos algumas versões de vários utilitários suportam arquivos de listagem separados por NULs em vez de novas linhas, há pelo menos find -print0 , sort -z e grep -Z nas versões GNU dessas ferramentas. Em xargs , o sinalizador é --null ou -0 . Então:

produce-list-of-files -0 | xargs -0 do-something-with

Um exemplo é executado com cat e, bem, ls -l :

$ touch "abba acdc" "foo bar" $'new\nline'
$ echo -en "abba acdc
do-something-with $(produce-list-of-files)
foo bar
produce-list-of-files | xargs do-something-with
new\nline
produce-list-of-files | xargs -d '\n' do-something-with
" > list $ cat list | xargs -0 ls -l -rw-r--r-- 1 itvirta itvirta 0 Aug 2 01:23 abba acdc -rw-r--r-- 1 itvirta itvirta 0 Aug 2 01:23 foo bar -rw-r--r-- 1 itvirta itvirta 0 Aug 2 01:23 new?line
    
por 02.08.2016 / 00:29
1

Se você tiver um comando que produza uma lista separada por novas linhas de itens, como nomes de arquivos, e os próprios itens podem conter novas linhas, então você não pode chegar lá a partir daqui.

Alguns comandos em alguns sistemas podem produzir ou gerenciar listas delimitadas por bytes nulos em vez de novas linhas, por exemplo, GNU grep ( grep -Z ), GNU sed ( sed -z ), GNU awk ( gawk RS='find -print0' ORS='xargs -0' ), GNU e BSD find ( set -f ),… Você pode então usar IFS para executar um comando nos itens de a lista delimitada por nulos.

find … -print0 | sed -z … | xargs -0 frob …

Se você estiver disposto a assumir que os nomes de arquivos não contêm novas linhas, poderá usar o operador split + glob, ou seja, expansão sem nome, para dividir a lista delimitada por nova linha. Você precisa desativar o gobbing com set -f e definir os caracteres delimitadores para dividir ( IFS variable) em uma nova linha.

IFS='
'; set -f
ls -l -- $(grep -l 'a certain word' *)
set +f; unset IFS

A última linha restaura as configurações padrão. Salvar e restaurar com precisão as configurações de %code% e %code% pode ser feito, mas é um problema.

Se você precisar salvar a saída para processamento posterior, poderá dividi-la no ponto de definição e armazenar os dados em uma matriz ou dividi-la no ponto de uso e armazenar os dados em uma string. Com uma matriz (que requer ksh, bash ou zsh):

IFS='
'; set -f
files=($(grep -l 'a certain word' *))
set +f; unset IFS
…
ls -l -- "${files[@]}"

Com uma string:

files=$(grep -l 'a certain word' *)
IFS='
'; set -f
ls -l -- $files
set +f; unset IFS

Veja também Why meu shell script engasga com espaço em branco ou outros caracteres especiais?

    
por 02.08.2016 / 02:10