Pergunta

Eu estou tentando copiar um monte de arquivos abaixo de um diretório e uma série de arquivos têm espaços e aspas simples em seus nomes. Quando tento find encadear e grep com xargs, eu recebo o seguinte erro:

find .|grep "FooBar"|xargs -I{} cp "{}" ~/foo/bar
xargs: unterminated quote

Todas as sugestões para uma utilização mais robusto de xargs?

Esta é a Mac OS X 10.5.3 (Leopard) com xargs BSD.

Foi útil?

Solução

Você pode combinar tudo isso em um único comando find:

find . -iname "*foobar*" -exec cp -- "{}" ~/foo/bar \;

Isto irá lidar com nomes de arquivos e diretórios com espaços entre eles. Você pode usar -name para obter resultados maiúsculas de minúsculas.

Nota: A bandeira -- passado para impede cp TI de processamento de arquivos começando com - como opções

.

Outras dicas

find . -print0 | grep --null 'FooBar' | xargs -0 ...

Eu não sei sobre se suportes grep --null, nem se suportes xargs -0, no Leopard, mas no GNU é tudo de bom.

A maneira mais fácil de fazer o seu autor quer é mudar o delimitador a partir de qualquer espaço em branco para apenas o caráter de fim-de-linha como esta:

find whatever ... | xargs -d "\n" cp -t /var/tmp

Este é mais eficiente, pois ele não é executado "CP" várias vezes:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar

Eu corri para o mesmo problema. Aqui está como eu resolvido-lo:

find . -name '*FoooBar*' | sed 's/.*/"&"/' | xargs cp ~/foo/bar

Eu costumava sed para substituir cada linha de entrada com a mesma linha, mas cercado por aspas duplas. A partir da página homem sed, " ... Um comercial (` `& '') aparece na substituição é substituído pela cadeia correspondente ao RE ... " - neste caso, .*, toda a linha.

Isto resolve o erro xargs: unterminated quote.

Este método funciona em Mac OS X v10.7.5 (Lion):

find . | grep FooBar | xargs -I{} cp {} ~/foo/bar

Eu também testou a sintaxe exata que você postou. Que também trabalhou bem em 10.7.5.

Só não use xargs. É um programa puro, mas não vão bem com find quando confrontados com casos não-triviais.

Aqui é uma solução portátil (POSIX), ou seja, uma que não requer extensões específicas find, xargs ou GNU cp:

find . -name "*FooBar*" -exec sh -c 'cp -- "$@" ~/foo/bar' sh {} +

Observe o + terminando em vez do ; mais usual.

Esta solução:

  • maneja bem arquivos e diretórios com espaços incorporados, novas linhas ou qualquer personagens exóticos.

  • funciona em qualquer sistema Unix e Linux, mesmo aqueles que não fornecer o conjunto de ferramentas GNU.

  • não usa xargs que é um bom e um programa útil, mas exige muito de ajustes e não padrão recursos para adequadamente saída alça find.

  • também é mais eficiente (leia mais rápido ) do que o aceito e a maioria se não todas as outras respostas.

Note também que, apesar do que é afirmado em algumas outras respostas ou comentários citando {} é inútil (a menos que você estiver usando o fishshell exótica).

olhar para usar a opção de linha de comando --null para xargs com a opção -print0 no achado.

Para aqueles que se baseia em comandos, que não seja descoberta, por exemplo ls:

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
find | perl -lne 'print quotemeta' | xargs ls -d

Eu acredito que isso vai funcionar de forma confiável para qualquer caractere, exceto alimentação de linha (e eu suspeito que se você tem linha feeds em seus nomes, então você tem problemas piores do que isso). Ele não requer findutils GNU, apenas Perl, por isso deve funcionar muito-muito em qualquer lugar.

Eu descobri que a seguinte sintaxe funciona bem para mim.

find /usr/pcapps/ -mount -type f -size +1000000c | perl -lpe ' s{ }{\\ }g ' | xargs ls -l | sort +4nr | head -200

Neste exemplo, eu estou olhando para os 200 maiores arquivos de mais de 1.000.000 bytes no sistema de arquivos montados em "/ usr / pcapps".

O Perl linha-liner entre "encontrar" e "xargs" escapes / cita cada espaço em branco para que "xargs" passa qualquer nome de arquivo com espaços em branco incorporados para "ls" como um único argumento.

Esteja ciente de que a maioria das opções discutidas em outras respostas não são padrão em plataformas que não utilizam os utilitários GNU (Solaris, AIX, HP-UX, por exemplo). Veja a POSIX especificação para comportamento xargs 'standard'.

Eu também acho o comportamento de xargs pelo qual ele é executado o comando, pelo menos uma vez, mesmo sem entrada, para ser um incômodo.

Eu escrevi a minha própria versão privada de xargs (xargl) para lidar com os problemas de espaços em nomes (somente novas linhas separado - embora a 'encontrar ... -print0' e 'xargs -0' combinação é dado bastante elegante que nomes de arquivo não pode conter ASCII NUL caracteres '\ 0' meu xargl não é tão completa quanto seria necessário para ser para valer a pena publicação -.. especialmente desde GNU tem instalações que são pelo menos tão boa

Com Bash (não POSIX), você pode usar a substituição de processo para obter a linha atual dentro de uma variável. Isso permite que você usar aspas para escapar caracteres especiais:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)

Para mim, eu estava tentando fazer algo um pouco diferente. Eu queria copiar meus arquivos .txt em minha pasta tmp. Os nomes de arquivos .txt contêm espaços e apóstrofo caracteres. Isso funcionou no meu Mac.

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/

Se encontrar e xarg versões em seu sistema não suporta -print0 e -0 interruptores (por exemplo AIX encontrar e xargs) que você pode usar este código terrivelmente procurando:

 find . -name "*foo*" | sed -e "s/'/\\\'/g" -e 's/"/\\"/g' -e 's/ /\\ /g' | xargs cp /your/dest

Aqui sed vai cuidar de escapar os espaços e citações para xargs.

Testado em AIX 5.3

Eu criei um script pequeno wrapper portátil chamado "xargsL" em torno "xargs", que aborda a maioria dos problemas.

Ao contrário do xargs, xargsL aceita um caminho por linha. Os caminhos podem conter qualquer caractere, exceto (obviamente) de nova linha ou NUL bytes.

Sem citar é permitido ou suportado na lista de arquivos - seus nomes de arquivo pode conter todos os tipos de espaços em branco, barras invertidas, acentos graves, a Shell caracteres curinga e similares -. XargsL irá processá-los como caracteres literais, nenhum dano feito

Como um recurso bônus adicional, xargsL irá não executar o comando uma vez se não houver entrada!

Observe a diferença:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Todos os argumentos dados a xargsL serão repassados ??para xargs.

Aqui está o script shell "xargsL" POSIX:

#! /bin/sh
# Line-based version of "xargs" (one pathname per line which may contain any
# amount of whitespace except for newlines) with the added bonus feature that
# it will not execute the command if the input file is empty.
#
# Version 2018.76.3
#
# Copyright (c) 2018 Guenther Brunthaler. All rights reserved.
#
# This script is free software.
# Distribution is permitted under the terms of the GPLv3.

set -e
trap 'test $? = 0 || echo "$0 failed!" >& 2' 0

if IFS= read -r first
then
        {
                printf '%s\n' "$first"
                cat
        } | sed 's/./\\&/g' | xargs ${1+"$@"}
fi

Coloque o script em algum diretório em seu $ PATH e não se esqueça de

$ chmod +x xargsL

o script lá para torná-lo executável.

o bill_starr Perl versão não irá funcionar bem para novas linhas incorporadas (somente lida com espaços). Para aqueles em v.g. Solaris, onde você não tem as ferramentas GNU, uma versão mais completa pode ser (usando sed) ...

find -type f | sed 's/./\\&/g' | xargs grep string_to_find

ajustar as encontrar e grep argumentos ou outros comandos que você precisar, mas o sed irá corrigir seus newlines incorporados / espaços / guias.

Eu costumava a resposta de Bill Estrela ligeiramente modificada no Solaris:

find . -mtime +2 | perl -pe 's{^}{\"};s{$}{\"}' > ~/output.file

Isto irá colocar aspas em torno de cada linha. Eu não usar o 'l' opção embora provavelmente iria ajudar.

A lista de arquivos que eu estava indo embora possa ter '-', mas não novas linhas. Eu não usei o arquivo de saída com quaisquer outros comandos como eu quero rever o que foi encontrado antes que eu só começar maciçamente excluí-los através xargs.

Eu joguei com isso um pouco, começou a contemplar xargs modificando, e percebi que para o tipo de caso de uso que estamos falando aqui, uma reimplementação simples na Python é uma idéia melhor.

Por um lado, ter ~ 80 linhas de código para toda a meios coisa é fácil de descobrir o que está acontecendo, e se o comportamento diferente é necessária, você pode apenas cortá-lo em um novo roteiro em menos tempo do que leva para obter uma resposta em algum lugar como Stack Overflow.

https://github.com/johnallsup/jda- Misc-scripts / blob / master / yargs e https://github.com/johnallsup/jda-misc-scripts/blob/master/zargs.py .

Com yargs como escrito (e Python 3 instalado), você pode digitar:

find .|grep "FooBar"|yargs -l 203 cp --after ~/foo/bar

para fazer as 203 copiar arquivos de uma vez. (Aqui 203 é apenas um espaço reservado, é claro, e usando um número estranho como 203 deixa claro que este número não tem outro significado.)

Se você realmente quer algo mais rápido e sem a necessidade de Python, tomar zargs e yargs como protótipos e reescrever em C ++ ou C.

Você pode precisar grep diretório Foobar como:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .

desafio Frame - você está perguntando como usar xargs. A resposta é:. Você não usar xargs, porque você não precisa dele

O comentário por user80168 descreve uma maneira de fazer isso diretamente com cp, sem chamar cp para cada arquivo:

find . -name '*FooBar*' -exec cp -t /tmp -- {} +

Isso funciona porque:

  • a bandeira cp -t permite dar o diretório de destino perto do início do cp, em vez de perto do fim. De man cp:
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • A bandeira -- diz cp para interpretar tudo depois como um nome de arquivo, e não uma bandeira, por isso arquivos começando com - ou -- fazer cp não confundir; você ainda precisa isto porque os personagens - / -- são interpretados por cp, enquanto todos os outros caracteres especiais são interpretados pelo shell.

  • A variante find -exec command {} + essencialmente faz o mesmo que xargs. De man find:

   -exec command {} +                                                     
         This  variant  of the -exec action runs the specified command on
         the selected files, but the command line is built  by  appending
         each  selected file name at the end; the total number of invoca‐
         matched  files.   The command line is built in much the same way
         that xargs builds its command lines.  Only one instance of  `{}'
         is  allowed  within the command, and (when find is being invoked
         from a shell) it should be quoted (for example, '{}') to protect
         it  from  interpretation  by shells.  The command is executed in
         the starting directory.  If any invocation  returns  a  non-zero
         value  as exit status, then find returns a non-zero exit status.
         If find encounters an error, this can sometimes cause an immedi‐
         ate  exit, so some pending commands may not be run at all.  This
         variant of -exec always returns true.

Ao utilizar este na descoberta diretamente, isto evita a necessidade de um tubo ou uma chamada de shell, de modo que você não precisa se preocupar com quaisquer personagens desagradáveis ??em nomes de arquivos.

Se você estiver usando Bash, você pode converter stdout para uma matriz de linhas por mapfile:

find . | grep "FooBar" | (mapfile -t; cp "${MAPFILE[@]}" ~/foobar)

Os benefícios são:

  • É construído-in, por isso é mais rápido.
  • Execute o comando com todos os nomes de arquivos de uma só vez, por isso é mais rápido.
  • Você pode acrescentar outros argumentos para os nomes de arquivo. Para cp, você também pode:

    find . -name '*FooBar*' -exec cp -t ~/foobar -- {} +
    

    No entanto, alguns comandos não têm essa característica.

As desvantagens:

  • Talvez não escala bem se há muitos nomes de arquivo. (O limite? Eu não sei, mas eu tinha testado com arquivo de lista de 10 MB, que inclui 10000 + nomes de arquivos sem nenhum problema, sob Debian)

Bem ... quem sabe se Bash está disponível no OS X?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top