Pregunta

Estoy intentando copiar un montón de archivos debajo de un directorio y varios de ellos tienen espacios y comillas simples en sus nombres. Cuando intento enlazar find y grep con xargs , aparece el siguiente error:

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

¿Alguna sugerencia para un uso más robusto de xargs?

Esto está en Mac & nbsp; OS & nbsp; X 10.5.3 (Leopard) con BSD xargs .

¿Fue útil?

Solución

Puedes combinar todo eso en un solo comando find :

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

Esto manejará los nombres de archivos y directorios con espacios en ellos. Puede usar -name para obtener resultados que distingan entre mayúsculas y minúsculas.

Nota: la marca - que se pasa a cp impide que se procesen archivos que comienzan con - como opciones.

Otros consejos

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

No sé si grep admite --null , ni si xargs es compatible con -0 , en Leopard, pero en GNU todo está bien.

La forma más fácil de hacer lo que quiere el póster original es cambiar el delimitador de cualquier espacio en blanco al carácter de fin de línea como este:

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

Esto es más eficiente ya que no se ejecuta " cp " varias veces:

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

Me encontré con el mismo problema. Así es como lo resolví:

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

Utilicé sed para sustituir cada línea de entrada con la misma línea, pero entre comillas dobles. Desde la página del manual de sed , " ... Un símbolo "(& amp; '') que aparece en el reemplazo se reemplaza por la cadena que coincide con el RE ... " - en este caso, . * , la línea completa.

Esto resuelve el error xargs: cita no terminada .

Este método funciona en Mac & nbsp; OS & nbsp; X & nbsp; v10.7.5 (Lion):

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

También probé la sintaxis exacta que publicaste. Eso también funcionó bien en 10.7.5.

Simplemente no uses xargs . Es un programa estupendo, pero no va bien con find cuando se enfrenta con casos no triviales.

Aquí hay una solución portátil (POSIX), es decir, una que no requiere find , xargs o cp Extensiones específicas de GNU:

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

Tenga en cuenta el + final en lugar del ; más habitual.

Esta solución:

  • maneja correctamente los archivos y directorios con espacios incrustados, nuevas líneas o cualquier carácter exótico.

  • funciona en cualquier sistema Unix y Linux, incluso aquellos que no proporcionan el kit de herramientas GNU.

  • no usa xargs , que es un programa agradable y útil, pero requiere demasiados ajustes y características no estándar para manejar adecuadamente la salida de find .

  • también es más eficiente (lea más rápido ) que las respuestas aceptadas y la mayoría, si no todas, las demás.

También tenga en cuenta que a pesar de lo que se indica en algunas otras respuestas o comentarios que citan {} es inútil (a menos que esté utilizando la exótica shell fish ).

Observe el uso de la opción de línea de comandos --null para xargs con la opción -print0 en la búsqueda.

Para aquellos que confían en comandos, aparte de find, por ejemplo, ls :

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

Creo que esto funcionará de manera confiable para cualquier personaje, excepto para el salto de línea (y sospecho que si tienes feeds de línea en tus nombres de archivo, entonces tienes problemas más graves que esto). No requiere recursos de búsqueda de GNU, solo Perl, por lo que debería funcionar prácticamente en cualquier lugar.

Descubrí que la siguiente sintaxis funciona bien para mí.

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

En este ejemplo, estoy buscando los 200 archivos más grandes de más de 1,000,000 de bytes en el sistema de archivos montado en " / usr / pcapps " ;.

El trazo de línea Perl entre " buscar " y " xargs " escapa / cita cada espacio en blanco así que " xargs " pasa cualquier nombre de archivo con espacios en blanco incrustados a " ls " como un solo argumento.

Tenga en cuenta que la mayoría de las opciones analizadas en otras respuestas no son estándar en plataformas que no utilizan las utilidades GNU (Solaris, AIX, HP-UX, por ejemplo). Consulte la especificación POSIX para el comportamiento "estándar" de xargs.

También encuentro que el comportamiento de xargs por el que ejecuta el comando al menos una vez, incluso sin entrada, es una molestia.

Escribí mi propia versión privada de xargs (xargl) para lidiar con los problemas de los espacios en los nombres (solo las nuevas líneas son separadas, aunque la combinación 'find ... -print0' y 'xargs -0' es bastante buena, dado que los nombres de archivo no pueden contener caracteres ASCII NUL '\ 0'. Mi xargl no es tan completo como debería ser para que valga la pena su publicación, especialmente porque GNU tiene instalaciones que son al menos tan buenas.

Con Bash (no POSIX) puede usar la sustitución de procesos para obtener la línea actual dentro de una variable. Esto le permite usar comillas para escapar de caracteres especiales:

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

Para mí, estaba tratando de hacer algo un poco diferente. Quería copiar mis archivos .txt en mi carpeta tmp. Los nombres de archivo .txt contienen espacios y caracteres de apóstrofe. Esto funcionó en mi Mac.

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

Si las versiones find y xarg en su sistema no admiten los conmutadores -print0 y -0 (por ejemplo, AIX find y xargs), puede usar este código de aspecto terrible :

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

Aquí sed se encargará de escapar de los espacios y las citas de xargs.

Probado en AIX 5.3

Creé un pequeño script de contenedor portátil llamado " xargsL " alrededor de " xargs " que aborda la mayoría de los problemas.

Al contrario de xargs, xargsL acepta un nombre de ruta por línea. Las rutas de acceso pueden contener cualquier carácter excepto (obviamente) nueva línea o NUL bytes.

No se permiten ni se admiten citas en la lista de archivos: los nombres de los archivos pueden contener todo tipo de espacios en blanco, barras invertidas, comillas invertidas, caracteres comodín de shell y similares: xargsL los procesará como caracteres literales, sin daño alguno.

Como una característica adicional, xargsL no ejecutará el comando una vez si no hay entrada.

Note la diferencia:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Cualquier argumento dado a xargsL se pasará a xargs.

Aquí está el " xargsL " POSIX shell script:

#! /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 "<*> failed!" >& 2' 0

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

Coloque el script en algún directorio de su $ PATH y no se olvide de

$ chmod + x xargsL

la secuencia de comandos para que sea ejecutable.

la versión Perl de bill_starr no funcionarán bien para las nuevas líneas incrustadas (solo se ocupa de los espacios). Para aquellos en por ejemplo Solaris donde no tiene las herramientas GNU, una versión más completa podría ser (usando sed) ...

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

ajuste los argumentos de búsqueda y grep u otros comandos que necesite, pero el sed solucionará sus líneas / espacios / tabulaciones incrustados.

Utilicé respuesta de Bill Star ligeramente modificada en Solaris:

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

Esto pondrá comillas alrededor de cada línea. No utilicé la opción '-l', aunque probablemente ayudaría.

La lista de archivos a la que me dirigía podría tener '-', pero no nuevas líneas. No he usado el archivo de salida con ningún otro comando, ya que quiero revisar lo que se encontró antes de comenzar a eliminarlos masivamente a través de xargs.

Jugué un poco con esto, comencé a contemplar la modificación de xargs y me di cuenta de que para el tipo de caso de uso del que estamos hablando aquí, una simple reimplementación en Python es una mejor idea.

Por un lado, tener ~ 80 líneas de código para todo significa que es fácil descubrir qué está sucediendo, y si se requiere un comportamiento diferente, puedes piratearlo en un nuevo script en menos tiempo. toma para obtener una respuesta en algún lugar como Stack & nbsp; Overflow.

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

Con yargs como está escrito (y Python 3 instalado) puede escribir:

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

para hacer la copia de 203 archivos a la vez. (Aquí, 203 es solo un marcador de posición, por supuesto, y usar un número extraño como 203 deja claro que este número no tiene otro significado).

Si realmente quieres algo más rápido y sin la necesidad de Python, toma zargs y yargs como prototipos y reescribe en C ++ o C.

Es posible que necesites grep el directorio de Foobar como:

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

Desafío de marco & # 8212; estás preguntando cómo usar xargs. La respuesta es: no utiliza xargs, porque no lo necesita.

El el comentario de user80168 describe una manera de hacer esto directamente con cp, sin llamar a cp para cada archivo:

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

Esto funciona porque:

  • el indicador cp -t permite dar al directorio de destino cerca del comienzo de cp , en lugar de cerca del final. Desde man cp :
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • El indicador - le dice a cp que interprete todo después como un nombre de archivo, no como un indicador, así que los archivos que comienzan con - o - no confunda cp ; aún necesita esto porque los caracteres - / - son interpretados por cp , mientras que el resto de caracteres especiales son interpretados por el shell.

  • La variante find -exec command {} + esencialmente hace lo mismo 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.

Al usar esto en encontrar directamente, esto evita la necesidad de una invocación de shell o canal, por lo que no es necesario preocuparse por los caracteres desagradables en los nombres de archivo.

Si está utilizando Bash, puede convertir stdout a una matriz de líneas mediante mapfile :

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

Los beneficios son:

  • Está integrado, por lo que es más rápido.
  • Ejecute el comando con todos los nombres de archivos de una sola vez, para que sea más rápido.
  • Puede agregar otros argumentos a los nombres de archivo. Para cp , también puede:

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

    sin embargo, algunos comandos no tienen dicha función.

Las desventajas:

  • Tal vez no se escale bien si hay demasiados nombres de archivos. (¿El límite? No lo sé, pero lo había probado con 10 & nbsp; archivo de lista de MB que incluye más de 10000 nombres de archivos sin problemas, bajo Debian)

Bueno ... ¿quién sabe si Bash está disponible en OS X?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top