bash shell unix spaces in-place

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



shell unix (18)

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

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


Advertencia: Esto romperá su repo.

Esto dañará los archivos binarios , incluidos los de svn , .git ! Lea los comentarios antes de usar!

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

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

Desventajas:

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

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

Esto no suele ser lo que quieres.

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

En teoría, podría pasar muchas opciones de exclusión para find o 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. Saltar archivos sobre un cierto tamaño.
  2. Detecte si un archivo es binario comprobando la presencia de un byte NULO.
  3. Solo reemplace las pestañas al inicio de un archivo ( expand hace esto, sed no).

Por lo que 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/n con /n , agregar un /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 usar shell globbing no es una forma sólida de hacer esto, porque tarde o temprano terminará con más archivos de los que caben en ARG_MAX (en Los sistemas Linux modernos son 128k, lo que puede parecer mucho, pero tarde o temprano no es 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)


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

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


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

Ejecute el script desde 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;


El reemplazo simple con sed está bien, pero no es la mejor solución posible. Si hay espacios "adicionales" entre las pestañas, seguirán estando allí después de la sustitución, por lo que los márgenes serán irregulares. 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 expand a cada archivo Java en el árbol de directorio actual. Elimine / reemplace el argumento -name si está apuntando a otros tipos de archivos. Como lo menciona uno de los comentarios, tenga mucho cuidado cuando elimine -name o use un comodín débil. Puede fácilmente aniquilar el repositorio y otros archivos ocultos sin intención. Por eso la respuesta original incluía esto:

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


El uso de expand como se sugiere en otras respuestas parece ser el enfoque más lógico para esta tarea solo.

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

Si se utiliza Bash 4.0 o superior, se puede usar globstar builtin globstar para buscar de forma recursiva con ** .

Con la versión 4.1 o superior de GNU Awk, 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


Me gusta el ejemplo "encontrar" de 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 desea que esté en silencio después de confiar en que funciona, simplemente suelte la -v en el comando sh al final.

Por supuesto, puedes elegir cualquier conjunto de archivos en el primer comando. Por ejemplo, liste solo un subdirectorio particular (o directorios) 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 una 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


Mi recomendación es usar:

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

Comentarios:

  1. Utilizar en lugar de edición. Mantener copias de seguridad en un VCS. No hay necesidad de producir archivos * .orig. Es una buena práctica diferenciar el resultado contra su último compromiso para asegurarse de que funcionó como se esperaba, en cualquier caso.
  2. sed es un editor de secuencias. Utilice ex para la edición en su lugar. Esto evita crear archivos temporales adicionales y generar shells para cada reemplazo como en la respuesta superior .
  3. ADVERTENCIA: Esto afecta a todas las pestañas, no solo a las que se usan para la sangría. Además, no hace el contexto de reemplazo de pestañas. Esto fue suficiente para mi caso de uso. Pero podría no ser aceptable para usted.
  4. EDITAR: una versión anterior de esta respuesta usaba find|xargs lugar de find -exec . Como lo señaló @ gniourf-gniourf, esto conduce a problemas con espacios, comillas y caracteres de control en los nombres de archivo cf. Wheeler .

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 {}'' /;


Pruebe la herramienta de línea de comandos 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 espacios en blanco (8 de forma predeterminada).
  • sponge es del paquete moreutils y evita borrar el archivo de entrada .

Finalmente, puede usar gexpand en OSX, después de instalar coreutils con Homebrew ( brew install coreutils ).


Puede usar el comando pr generalmente disponible (página de manual here ). Por ejemplo, para convertir pestañas en cuatro espacios, haga esto:

pr -t -e=4 file > file.expanded

  • -t suprime encabezados
  • -e=num expande las pestañas a espacios num

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 saltar 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 adecuada si tiene archivos de código que tienen pestañas incrustadas en cadenas literales

Puede usar el paquete find con tabs-to-spaces 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 carácter de tab con 2 spaces en cada archivo.


Recopilar los mejores comentarios de la respuesta de Gene , la mejor solución con diferencia, es mediante el uso de una sponge de joeyh.name/code/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
  • -iname es una coincidencia que no distingue entre mayúsculas y minúsculas (para los *.java y *.JAVA )
  • type -f solo encuentra archivos normales (no directorios, archivos binarios o enlaces simbólicos)
  • -exec bash -c ejecuta los siguientes comandos en una subshell para cada nombre de archivo, {}
  • expand -t 4 expande todos los TABs a 4 espacios
  • sponge absorbe la entrada estándar (de expand ) y escribe en un archivo (el mismo) *.

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

Ventaja : se conservan todos los permisos de archivo originales y no se utilizan archivos tmp intermedios.


Uno puede usar vim para eso:

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

Como lo dijo Carpetsmoker, reaparecerá de acuerdo con la configuración de vim . Y modelines en los archivos, en su caso. Además, reemplazará las pestañas no solo al principio de las líneas. Que no es lo que generalmente quieres. Por ejemplo, puede que tengas literales que contengan pestañas.


Usa el vim-way:

$ ex +''bufdo retab'' -cxa **/*.*

  • Hacer la copia de seguridad! antes de ejecutar el comando anterior, ya que puede dañar sus archivos binarios.
  • Para usar globstar ( ** ) para la recursión, active by shopt -s globstar .
  • Para especificar un tipo de archivo específico, use por ejemplo: **/*.c

Para modificar la tabulación, agregue +''set ts=2'' .

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

Así que para una solución ligeramente mejor (mediante sustitución), intente:

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

O mediante el uso de ex editor + expand utilidad:

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

Para los espacios finales, consulte: ¿Cómo eliminar los espacios en blanco finales para varios archivos?

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

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


Utilicé astyle para volver a sangrar todo mi código C / C ++ después de encontrar tabulaciones y espacios mixtos. También tiene opciones para forzar un estilo de refuerzo particular si lo desea.


Utilice la barra invertida escapó 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 en lugar, en todos los archivos * .txt:

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

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

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

En un mac

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

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


Método amigable de repositorio 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" )

Actuar en todos los archivos bajo el directorio actual:

git-tab-to-space

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

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

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

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

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

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

chmod --reference mantiene los permisos de archivo sin cambios: https://unix.stackexchange.com/questions/20645/clone-ownership-and-permissions-from-another-file Desafortunadamente no puedo encontrar una alternativa POSIX sucinta .

Si tu base de código tuvo la loca idea de permitir pestañas crudas funcionales en cadenas, usa:

expand -i

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

Probado en Ubuntu 18.04.