¿Cómo puedo convertir pestañas a espacios en cada archivo de un directorio?


251

¿Cómo puedo convertir pestañas a espacios en cada archivo de un directorio (posiblemente de forma recursiva)?

Además, ¿hay alguna forma de establecer el número de espacios por pestaña?


¿Desea reemplazar pestañas en archivos o nombres de archivos?
cppcoder

3
pres una maravillosa utilidad para esto Mira esta respuesta .
codeforester

Respuestas:


69

Advertencia: Esto romperá tu repositorio.

Esta voluntad archivos binarios corruptos , incluyendo los menores svn, .git! Lea los comentarios antes de usar!

find . -iname '*.java' -type f -exec sed -i.orig 's/\t/ /g' {} +

El archivo original se guarda como [filename].orig.

Reemplace '* .java' con el final de archivo del tipo de archivo que está buscando. De esta manera, puede evitar la corrupción accidental de archivos binarios.

Desventajas:

  • Reemplazará las pestañas en todas partes en un archivo.
  • Tomará mucho tiempo si tiene un volcado de 5GB SQL en este directorio.

12
Para el espacio visual que es una mezcla de pestañas y espacios, este enfoque proporciona una expansión incorrecta.
pizza

77
También agregaría un emparejador de archivos como, por ejemplo, solo para archivos .php find ./ -iname "* .php" -type f -exec sed -i 's / \ t / / g' {} \;
Daniel Luca CleanUnicorn

98
¡NO USE SED! Si hay una pestaña incrustada en una cadena, puede terminar destrozando su código. Esto es lo que el comando de expansión debía manejar. Uso expand.
David W.

55
@DavidW. Simplemente actualizaría este comando para reemplazar solo las pestañas desde el comienzo de la línea. find ./ -type f -exec sed -i 's/^\t/####/g' {} \;. Pero no conocía el comando expandir, ¡muy útil!
Martin Konecny

29
¡NO UTILICE! Esta respuesta también destruyó mi repositorio local de git. Si tiene archivos que contienen pestañas y espacios mixtos, insertará secuencias de # 's. Use la respuesta de Gene o el comentario de Doge a continuación.
títere

344

El reemplazo simple con sedestá bien, pero no es la mejor solución posible. Si hay espacios "adicionales" entre las pestañas, todavía estarán allí después de la sustitución, por lo que los márgenes serán desiguales. Las pestañas expandidas en el medio de las líneas tampoco funcionarán correctamente. En bash, podemos decir en su lugar

find . -name '*.java' ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;

para aplicar expanda cada archivo Java en el árbol de directorios actual. Elimine / reemplace el -nameargumento si está apuntando a otros tipos de archivos. Como menciona uno de los comentarios, tenga mucho cuidado al quitar -nameo usar un comodín débil. Puede fácilmente clobber repositorio y otros archivos ocultos sin intención. Es por eso que la respuesta original incluía esto:

Siempre debe hacer una copia de seguridad del árbol antes de intentar algo como esto en caso de que algo salga mal.


2
@JeffreyMartinez Gran pregunta. gniourf_gniourf editó mi respuesta original el 11 de noviembre e hizo comentarios despectivos sobre no saber la forma correcta de usar {}. Parece que no sabía $0cuándo -cse usa. Luego, dimo414 cambió de mi uso de una temperatura en el directorio de conversión a /tmp, que será mucho más lento si /tmpestá en un punto de montaje diferente. Lamentablemente, no tengo una caja de Linux disponible para probar su $0propuesta. Pero creo que tienes razón.
Gene

1
@Gene, gracias por la aclaración, eso suena bien como stackoverflow: p. Sin embargo, mientras lo hago, agregaré que tuve que usar comillas alrededor de '* .java' para escapar correctamente de * .java.
Jeffrey Martinez

2
Si alguien está teniendo un error de 'operador primario u operador desconocido' de encontrar, entonces aquí está el comando completo que lo solucionará:find . -name '*.java' ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;
Doge

44
Pensé que esta respuesta no tenía suficientes comentarios, así que esta es la mía: si se usa spongedesde joeyh.name/code/moreutils , puede escribirfind . -name '*.py' ! -type d -exec bash -c 'expand -t 8 "$0" | sponge "$0"' {} \;
tokland el

8
No seas tonto y el uso find . -name '*', sólo destruyó mi repositorio git locales
Gautam

193

Prueba la herramienta de línea de comando expand.

expand -i -t 4 input | sponge output

dónde

  • -i se usa para expandir solo las pestañas iniciales en cada línea;
  • -t 4 significa que cada pestaña se convertirá en 4 caracteres de espacio en blanco (8 por defecto).
  • spongees del moreutilspaquete y evita borrar el archivo de entrada .

Finalmente, puede usar gexpanden OSX, después de instalar coreutilscon Homebrew ( brew install coreutils).


55
Es uno de de GNU_Core_Utilities
kev

32
Debe pasar -ia expandpara reemplazar solo las pestañas iniciales en cada línea. Esto ayuda a evitar el reemplazo de pestañas que podrían ser parte del código.
Preguntas de Quolonel

10
¿Qué tal para cada archivo en un directorio recursivamente?
ahnbizcad

44
Cada vez que intento usar esto, pone en blanco algunos (generalmente todos) de los archivos. : \
ThorSummoner

55
@ThorSummoner: si inputes el mismo archivo que outputbash clobbers el contenido incluso antes de comenzar expand. Así es como >funciona.
Robert Siemer

34

Recopilar los mejores comentarios de la respuesta de Gene , la mejor solución con diferencia, es usar spongefrom moreutils .

sudo apt-get install moreutils
# The complete one-liner:
find ./ -iname '*.java' -type f -exec bash -c 'expand -t 4 "$0" | sponge "$0"' {} \;

Explicación:

  • ./ está buscando recursivamente desde el directorio actual
  • -inamees una coincidencia entre mayúsculas y minúsculas (para ambos *.javay me *.JAVAgusta)
  • type -f encuentra solo archivos regulares (sin directorios, binarios o enlaces simbólicos)
  • -exec bash -c ejecutar los siguientes comandos en una subshell para cada nombre de archivo, {}
  • expand -t 4 expande todas las TAB a 4 espacios
  • spongeabsorber la entrada estándar (desde expand) y escribir en un archivo (el mismo) *.

NOTA : * Una redirección de archivo simple ( > "$0") no funcionará aquí porque sobrescribiría el archivo demasiado pronto .

Ventaja : se retienen todos los permisos de archivos originales y no tmpse utilizan archivos intermedios .


2
TIL: el maravilloso comando de esponja, después de 15 años de usar Linux. Gracias misterioso caballero de internet.
sscarduzio

19

Utilice barra invertida con escape sed.

En linux:

  • Reemplace todas las pestañas con 1 guión in situ, en todos los archivos * .txt:

    sed -i $'s/\t/-/g' *.txt
  • Reemplace todas las pestañas con 1 espacio in situ, en todos los archivos * .txt:

    sed -i $'s/\t/ /g' *.txt
  • Reemplace todas las pestañas con 4 espacios in situ, en todos los archivos * .txt:

    sed -i $'s/\t/    /g' *.txt

En una mac:

  • Reemplace todas las pestañas con 4 espacios in situ, en todos los archivos * .txt:

    sed -i '' $'s/\t/    /g' *.txt

2
@ Машаsed -i '' $'s/\t/ /g' $(find . -name "*.txt")
xyzale

Esta respuesta parece ser la más simple.
Yan King Yin

6

Puede usar el prcomando generalmente disponible (página man aquí ). Por ejemplo, para convertir pestañas a cuatro espacios, haga esto:

pr -t -e=4 file > file.expanded
  • -t suprime encabezados
  • -e=numexpande pestañas a numespacios

Para convertir todos los archivos en un árbol de directorios de forma recursiva, mientras se saltan los archivos binarios:

#!/bin/bash
num=4
shopt -s globstar nullglob
for f in **/*; do
  [[ -f "$f" ]]   || continue # skip if not a regular file
  ! grep -qI "$f" && continue # skip binary files
  pr -t -e=$num "$f" > "$f.expanded.$$" && mv "$f.expanded.$$" "$f"
done

La lógica para omitir archivos binarios es de esta publicación .

NOTA:

  1. Hacer esto podría ser peligroso en un repositorio git o svn
  2. Esta no es la solución correcta si tiene archivos de código que tienen pestañas incrustadas en literales de cadena

1
¿Alguna ventaja sobre expanddado que ambos son POSIX? Por ejemplo, ¿tiene una opción de cambio en línea? Git safety en: stackoverflow.com/a/52136507/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

5

¿Cómo puedo convertir pestañas a espacios en cada archivo de un directorio (posiblemente de forma recursiva)?

Esto generalmente no es lo que quieres.

¿Quieres hacer esto para imágenes png? Archivos PDF? El directorio .git? Tu Makefile(que requiere pestañas)? ¿Un volcado de 5GB SQL?

En teoría, podría pasar muchas opciones de exclusión findo cualquier otra cosa que esté utilizando; pero esto es frágil y se romperá tan pronto como agregue otros archivos binarios.

Lo que quieres es al menos:

  1. Omitir archivos de cierto tamaño.
  2. Detecta si un archivo es binario comprobando la presencia de un byte NULL.
  3. Solo reemplace las pestañas al comienzo de un archivo ( expandhace esto, sed no).

Hasta donde yo sé, no hay una utilidad "estándar" de Unix que pueda hacer esto, y no es muy fácil hacerlo con un shell de una sola línea, por lo que se necesita un script.

Hace un tiempo creé un pequeño script llamado sanitize_files que hace exactamente eso. También corrige algunas otras cosas comunes como reemplazar \r\ncon \n, agregar un final \n, etc.

Puede encontrar una secuencia de comandos simplificada sin las características adicionales y los argumentos de la línea de comandos a continuación, pero le recomiendo que use la secuencia de comandos anterior, ya que es más probable que reciba correcciones de errores y otras actualizaciones que esta publicación.

También me gustaría señalar, en respuesta a algunas de las otras respuestas aquí, que el uso de shell globbing no es una forma sólida de hacerlo, porque tarde o temprano terminarás con más archivos de los que caben ARG_MAX(en los modernos sistemas Linux es 128k, que puede parecer mucho, pero tarde o temprano es no suficiente).


#!/usr/bin/env python
#
# http://code.arp242.net/sanitize_files
#

import os, re, sys


def is_binary(data):
    return data.find(b'\000') >= 0


def should_ignore(path):
    keep = [
        # VCS systems
        '.git/', '.hg/' '.svn/' 'CVS/',

        # These files have significant whitespace/tabs, and cannot be edited
        # safely
        # TODO: there are probably more of these files..
        'Makefile', 'BSDmakefile', 'GNUmakefile', 'Gemfile.lock'
    ]

    for k in keep:
        if '/%s' % k in path:
            return True
    return False


def run(files):
    indent_find = b'\t'
    indent_replace = b'    ' * indent_width

    for f in files:
        if should_ignore(f):
            print('Ignoring %s' % f)
            continue

        try:
            size = os.stat(f).st_size
        # Unresolvable symlink, just ignore those
        except FileNotFoundError as exc:
            print('%s is unresolvable, skipping (%s)' % (f, exc))
            continue

        if size == 0: continue
        if size > 1024 ** 2:
            print("Skipping `%s' because it's over 1MiB" % f)
            continue

        try:
            data = open(f, 'rb').read()
        except (OSError, PermissionError) as exc:
            print("Error: Unable to read `%s': %s" % (f, exc))
            continue

        if is_binary(data):
            print("Skipping `%s' because it looks binary" % f)
            continue

        data = data.split(b'\n')

        fixed_indent = False
        for i, line in enumerate(data):
            # Fix indentation
            repl_count = 0
            while line.startswith(indent_find):
                fixed_indent = True
                repl_count += 1
                line = line.replace(indent_find, b'', 1)

            if repl_count > 0:
                line = indent_replace * repl_count + line

        data = list(filter(lambda x: x is not None, data))

        try:
            open(f, 'wb').write(b'\n'.join(data))
        except (OSError, PermissionError) as exc:
            print("Error: Unable to write to `%s': %s" % (f, exc))


if __name__ == '__main__':
    allfiles = []
    for root, dirs, files in os.walk(os.getcwd()):
        for f in files:
            p = '%s/%s' % (root, f)
            if do_add:
                allfiles.append(p)

    run(allfiles)


5

Me gusta el ejemplo "encontrar" anterior para la aplicación recursiva. Para adaptarlo para que no sea recursivo, solo cambiando los archivos en el directorio actual que coinciden con un comodín, la expansión de shell glob puede ser suficiente para pequeñas cantidades de archivos:

ls *.java | awk '{print "expand -t 4 ", $0, " > /tmp/e; mv /tmp/e ", $0}' | sh -v

Si lo desea en silencio después de confiar en que funciona, simplemente suelte -vel shcomando al final.

Por supuesto, puede elegir cualquier conjunto de archivos en el primer comando. Por ejemplo, enumere solo un subdirectorio (o directorios) particular de una manera controlada como esta:

ls mod/*/*.php | awk '{print "expand -t 4 ", $0, " > /tmp/e; mv /tmp/e ", $0}' | sh

O, a su vez, ejecute find (1) con alguna combinación de parámetros de profundidad, etc.

find mod/ -name '*.php' -mindepth 1 -maxdepth 2 | awk '{print "expand -t 4 ", $0, " > /tmp/e; mv /tmp/e ", $0}' | sh

1
El bloqueo de shell se romperá tarde o temprano, porque la cantidad total de nombres de archivo solo puede ser ARG_MAXlarga. Esto es 128k en sistemas Linux, pero he encontrado este límite las veces suficientes para no confiar en el bloqueo de shell.
Martin Tournoij

1
Realmente no necesitas adaptarlos. findse puede decir -maxdepth 1, y solo procesa las entradas del directorio que se está modificando, no todo el árbol.
ShadowRanger

4

Solía astylevolver a sangrar todo mi código C / C ++ después de encontrar pestañas y espacios mixtos. También tiene opciones para forzar un estilo de llave en particular si lo desea.


4

Se puede usar vimpara eso:

find -type f \( -name '*.css' -o -name '*.html' -o -name '*.js' -o -name '*.php' \) -execdir vim -c retab -c wq {} \;

Como dijo Carpetsmoker, se recuperará de acuerdo con su vimconfiguración. Y modelines en los archivos, si los hay. Además, reemplazará las pestañas no solo al comienzo de las líneas. Que no es lo que generalmente quieres. Por ejemplo, puede tener literales, que contienen pestañas.


:retabcambiará todas las pestañas de un archivo, no las del inicio. También depende de cuáles son sus :tabstopy :expandtabajustes están en el vimrc o modeline, por lo que este puede no funcionar en absoluto.
Martin Tournoij

@Carpetsmoker Buen punto sobre las pestañas al comienzo de las líneas. ¿Alguna de las soluciones aquí maneja este caso? En cuanto a la configuración tabstopy expandtab, funcionará si está utilizando vim. A menos que tenga líneas de modo en los archivos.
x-yuri

@ x-yuri buena pregunta, pero en general discutible. La mayoría de las personas no usan pestañas reales en literales.
Ricardo Cruz

4

Mi recomendación es usar:

find . -name '*.lua' -exec ex '+%s/\t/  /g' -cwq {} \;

Comentarios:

  1. Uso en el lugar de edición. Mantenga copias de seguridad en un VCS. No es necesario producir archivos * .orig. Es una buena práctica diferenciar el resultado de su último compromiso para asegurarse de que funcionó como se esperaba, en cualquier caso.
  2. sedes un editor de stream. Úselo expara la edición en el lugar. Esto evita crear archivos temporales adicionales y generar shells para cada reemplazo como en la respuesta superior .
  3. ADVERTENCIA: Esto interfiere con todas las pestañas, no solo con las que se usan para sangrar. Además, no reemplaza las pestañas según el contexto. Esto fue suficiente para mi caso de uso. Pero podría no ser aceptable para ti.
  4. EDITAR: se utilizó una versión anterior de esta respuesta en find|xargslugar de find -exec. Como señaló @ gniourf-gniourf, esto lleva a problemas con espacios, comillas y caracteres de control en los nombres de archivo cf. Wheeler .

expodría no estar disponible en todos los sistemas Unix. Sustituirlo con vi -epodría funcionar en más máquinas. Además, su expresión regular reemplaza cualquier número de caracteres de tabulación iniciales con dos espacios. Reemplace la expresión regular con +%s/\t/ /guna sangría de múltiples niveles para no destruir. Sin embargo, esto también afecta a los caracteres de tabulación que no se usan para la sangría.
Lukas Schmelzeisen

ex es parte de POSIX [1] por lo que debería estar disponible. Buen punto sobre la indendación multinivel. En realidad, había usado la /\t/ /variante en mis archivos, pero opté por /\t\+//no romper las pestañas sin sangría. ¡Perdió los problemas con la sangría múltiple! Actualizando la respuesta. [1] man7.org/linux/man-pages/man1/ex.1p.html#SEE%C2%A0ALSO
Heinrich Hartmann

2
Usarlo xargsde esta manera es inútil, ineficiente y está roto (piense en nombres de archivos que contengan espacios o comillas). ¿Por qué no usas findel -execinterruptor en su lugar?
gniourf_gniourf

Yo diría que los nombres de archivo con espacios y comillas están rotos; ) Si necesita soporte, optaría por: -print0opciones para encontrar / xargs. Me gusta xargs -execporque: a) Separación de preocupaciones b) se puede intercambiar con GNU en paralelo más fácilmente.
Heinrich Hartmann

Actualizado agregando @gniourf_gniourf comentarios.
Heinrich Hartmann

4

Para convertir todos los archivos Java de forma recursiva en un directorio para usar 4 espacios en lugar de una pestaña:

find . -type f -name *.java -exec bash -c 'expand -t 4 {} > /tmp/stuff;mv /tmp/stuff {}' \;

¿Cómo se diferencia respuesta de este que fue publicado hace 4 años?
PP

2
Tu respuesta también. De hecho, esta es una versión inferior de la respuesta de Gene: 1) La respuesta de Gene se encarga de los directorios con el mismo nombre. 2) No se mueve si falla la expansión.
PP

4

Puede usar findcon el tabs-to-spacespaquete para esto.

Primero, instale tabs-to-spaces

npm install -g tabs-to-spaces

luego, ejecute este comando desde el directorio raíz de su proyecto;

find . -name '*' -exec t2s --spaces 2 {} \;

Esto reemplazará cada tabcarácter con 2 spacesen cada archivo.


3

¿Ningún cuerpo mencionado rpl? Usando rpl puedes reemplazar cualquier cadena. Para convertir pestañas en espacios,

rpl -R -e "\t" "    "  .

muy simple.


1
Esto corrompió todos los archivos binarios en mi repositorio.
Aaron Franke

1
Un comando excelente, pero potencialmente peligroso con la opción recursiva y todos los archivos en la carpeta como se especifica anteriormente. Agregaría la opción --dry-run "por si acaso" para asegurarme de que está sentado en la carpeta correcta.
MortimerCat

2

El uso de expandlo sugerido en otras respuestas parece el enfoque más lógico solo para esta tarea.

Dicho esto, también se puede hacer con Bash y Awk en caso de que desee realizar otras modificaciones junto con él.

Si usa Bash 4.0 o superior, el shopt incorporado globstar puede usarse para buscar recursivamente con **.

Con GNU Awk versión 4.1 o superior, se pueden realizar modificaciones de archivos "in situ":

shopt -s globstar
gawk -i inplace '{gsub("\t","    ")}1' **/*.ext

En caso de que desee establecer el número de espacios por pestaña:

gawk -i inplace -v n=4 'BEGIN{for(i=1;i<=n;i++) c=c" "}{gsub("\t",c)}1' **/*.ext

2

Descargue y ejecute el siguiente script para convertir recursivamente las pestañas duras a pestañas suaves en archivos de texto sin formato.

Ejecute el script desde el interior de la carpeta que contiene los archivos de texto sin formato.

#!/bin/bash

find . -type f -and -not -path './.git/*' -exec grep -Iq . {} \; -and -print | while read -r file; do {
    echo "Converting... "$file"";
    data=$(expand --initial -t 4 "$file");
    rm "$file";
    echo "$data" > "$file";
}; done;

2

Método amigable del repositorio de Git

git-tab-to-space() (
  d="$(mktemp -d)"
  git grep --cached -Il '' | grep -E "${1:-.}" | \
    xargs -I'{}' bash -c '\
    f="${1}/f" \
    && expand -t 4 "$0" > "$f" && \
    chmod --reference="$0" "$f" && \
    mv "$f" "$0"' \
    '{}' "$d" \
  ;
  rmdir "$d"
)

Actúa sobre todos los archivos del directorio actual:

git-tab-to-space

Actúa solo en archivos C o C ++:

git-tab-to-space '\.(c|h)(|pp)$'

Es probable que desee esto especialmente debido a esos molestos Makefiles que requieren pestañas.

El comando git grep --cached -Il '':

  • enumera solo los archivos rastreados, por lo que no hay nada dentro .git
  • excluye directorios, archivos binarios (estarían dañados) y enlaces simbólicos (se convertirían en archivos normales)

como se explica en: ¿Cómo enumerar todos los archivos de texto (no binarios) en un repositorio git?

chmod --referencemantiene los permisos del archivo sin cambios: /unix/20645/clone-ownership-and-permissions-from-another-file Lamentablemente no puedo encontrar una alternativa sucinta de POSIX .

Si su base de código tuvo la loca idea de permitir pestañas en bruto funcionales en cadenas, use:

expand -i

y luego diviértete repasando todas las pestañas que no son de inicio de línea una por una, que puedes enumerar con: ¿Es posible obtener grep para pestañas?

Probado en Ubuntu 18.04.


-1

Convertir pestañas a espacio solo en archivos ".lua" [pestañas -> 2 espacios]

find . -iname "*.lua" -exec sed -i "s#\t#  #g" '{}' \;

Obviamente, la cantidad de espacio al que se expande una pestaña depende del contexto. Por lo tanto, sed es una herramienta completamente inapropiada para la tarea.
Sven

?? @Sven, mi comando sed hace lo mismo que el comando expand ( expand -t 4 input >output)
Makah

3
Por supuesto no. expand -t 4expandirá la pestaña a\tba 3 espacios y la pestaña aa\tba 2 espacios, tal como debería ser. expandtoma en cuenta el contexto de una pestaña, sedno reemplaza y reemplazará la pestaña con la cantidad de espacios que especifique, independientemente del contexto.
Sven

-1

Usa el vim-way:

$ ex +'bufdo retab' -cxa **/*.*
  • ¡Haz la copia de seguridad! antes de ejecutar el comando anterior, ya que puede dañar sus archivos binarios.
  • Para usar globstar( **) para la recursividad, actívela mediante shopt -s globstar.
  • Para especificar el tipo de archivo específico, utilice por ejemplo: **/*.c.

Para modificar el tabulador, agregue +'set ts=2'.

Sin embargo, el inconveniente es que puede reemplazar las pestañas dentro de las cadenas .

Entonces, para una solución ligeramente mejor (mediante el uso de sustitución), intente:

$ ex -s +'bufdo %s/^\t\+/  /ge' -cxa **/*.*

O usando exeditor + expandutilidad:

$ ex -s +'bufdo!%!expand -t2' -cxa **/*.*

Para espacios finales, vea: ¿Cómo eliminar espacios en blanco finales para múltiples archivos?


Puede agregar la siguiente función en su .bash_profile:

# Convert tabs to spaces.
# Usage: retab *.*
# See: https://stackoverflow.com/q/11094383/55075
retab() {
  ex +'set ts=2' +'bufdo retab' -cxa $*
}

Voté muchas respuestas en este hilo, no solo las suyas ;-) Las razones son: :retabpuede que no funcione en absoluto , el bloqueo de shell es una mala solución para este tipo de cosas , su :scomando reemplazará cualquier cantidad de pestañas con 2 espacios (que casi nunca quiero), comenzar ex solo para ejecutar un :!expandproceso es una tontería ...
Martin Tournoij

... y todas sus soluciones golpearán archivos binarios y similares (como archivos .png, archivos .pdf, etc.)
Martin Tournoij

Francamente, esta es una sugerencia horrible para la documentación: hay que conocer íntimamente una serie de sintaxis bastante opaca y problemas semánticos de varios programas para poder comprender esto.
Josip Rodin
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.