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:
- Saltar archivos sobre un cierto tamaño.
- Detecte si un archivo es binario comprobando la presencia de un byte NULO.
- 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:
- 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.
-
sed
es un editor de secuencias. Utiliceex
para la edición en su lugar. Esto evita crear archivos temporales adicionales y generar shells para cada reemplazo como en la respuesta superior . - 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.
- EDITAR: una versión anterior de esta respuesta usaba
find|xargs
lugar defind -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 paquetemoreutils
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 espaciosnum
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:
- Hacer esto podría ser peligroso en un repositorio git o svn.
- 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 (deexpand
) 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 byshopt -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.