Comment puis-je convertir des tabs en espaces dans chaque fichier d’un répertoire?

Comment puis-je convertir des tabulations en espaces dans chaque fichier d’un répertoire (éventuellement récursivement)?

En outre, existe-t-il un moyen de définir le nombre d’espaces par onglet?

Attention: Cela va casser votre repo.

Cela corrompra les fichiers binarys , y compris ceux sous svn , .git ! Lisez les commentaires avant d’utiliser!

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

Le fichier d’origine est enregistré sous le [filename].orig .

Inconvénients:

  • Remplacera les tabs partout dans un fichier.
  • Cela prendra beaucoup de temps si vous avez un dump SQL de 5 Go dans ce répertoire.

Le remplacement simple avec sed est correct, mais pas la meilleure solution possible. S’il y a des espaces “supplémentaires” entre les tabs, ils seront toujours là après la substitution, donc les marges seront irrégulières. Les tabs étendus au milieu des lignes ne fonctionneront pas correctement. En bash , on peut dire à la place

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

appliquer à tous les fichiers Java de l’arborescence de répertoires en cours. Supprimez / remplacez l’argument -name si vous ciblez d’autres types de fichiers. Comme l’un des commentaires le mentionne, soyez très prudent lorsque vous supprimez -name ou utilisez un caractère générique faible. Vous pouvez facilement débloquer le référentiel et les autres fichiers cachés sans intention. C’est pourquoi la réponse originale comprenait ceci:

Vous devez toujours faire une copie de sauvegarde de l’arborescence avant d’essayer quelque chose comme cela au cas où quelque chose ne va pas.

Essayez l’outil d’ expand ligne de commande.

 expand -i -t 4 input | sponge output 

  • -i est utilisé pour développer uniquement les tabs principaux sur chaque ligne;
  • -t 4 signifie que chaque onglet sera converti en 4 caractères d’espacement (8 par défaut).
  • sponge provient du paquet moreutils et évite de moreutils le fichier d’entrée .

Enfin, vous pouvez utiliser gexpand sur OSX, après avoir installé coreutils avec Homebrew ( brew install coreutils ).

Utilisez sed antislash.

Sous linux:

  • Remplacez tous les tabs par 1 trait d’union dans tous les fichiers * .txt:

     sed -i $'s/\t/-/g' *.txt 
  • Remplacez tous les tabs par 1 espace dans tous les fichiers * .txt:

     sed -i $'s/\t/ /g' *.txt 
  • Remplacez tous les tabs par 4 espaces dans tous les fichiers * .txt:

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

Sur un mac:

  • Remplacez tous les tabs par 4 espaces dans tous les fichiers * .txt:

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

Collecter les meilleurs commentaires de la réponse de Gene , la meilleure solution de loin, est d’utiliser l’ sponge de moreutils .

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

Explication:

  • ./ recherche récursivement dans le répertoire courant
  • -iname est une correspondance insensible à la casse (pour les deux *.java et *.JAVA aime)
  • type -f ne trouve que des fichiers réguliers (pas de répertoires, de binarys ou de liens symboliques)
  • -exec bash -c exécute les commandes suivantes dans un sous-shell pour chaque nom de fichier, {}
  • expand -t 4 étend toutes les tabulations à 4 espaces
  • sponge absorber l’entrée standard (à partir de l’ expand ) et écrire dans un fichier (le même) *.

REMARQUE : * Une simple redirection de fichier ( > "$0" ) ne fonctionnera pas ici car cela écraserait le fichier trop tôt .

Avantage : toutes les permissions de fichiers d’origine sont conservées et aucun fichier tmp intermédiaire n’est utilisé.

J’aime l’exemple “find” ci-dessus pour l’application récursive. Pour l’adapter pour ne pas être récursif, en ne modifiant que les fichiers du répertoire courant qui correspondent à un caractère générique, l’extension glob du shell peut suffire pour de petites quantités de fichiers:

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

Si vous voulez le garder silencieux après que vous ayez confiance que cela fonctionne, déposez simplement -v sur la commande sh à la fin.

Bien entendu, vous pouvez choisir n’importe quel jeu de fichiers dans la première commande. Par exemple, ne listez qu’un ou plusieurs sous-répertoires particuliers de manière contrôlée, comme ceci:

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

Ou à son tour exécuter find (1) avec une combinaison de parameters de profondeur, etc.:

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

Comment puis-je convertir des tabulations en espaces dans chaque fichier d’un répertoire (éventuellement récursivement)?

Ce n’est généralement pas ce que vous voulez.

Voulez-vous faire cela pour les images png? Fichiers PDF? Le répertoire .git? Votre Makefile (qui nécessite des tabs)? Un dump SQL de 5 Go?

Vous pourriez, en théorie, passer beaucoup d’options d’exclusion à find ou autre chose que vous utilisez; mais cela est fragile et se brisera dès que vous appendez d’autres fichiers binarys.

Ce que vous voulez, c’est au moins:

  1. Ignorer les fichiers d’une certaine taille
  2. Détecter si un fichier est binary en vérifiant la présence d’un octet NULL.
  3. Ne remplacez que les tabs au début d’un fichier (l’ expand ne le fait pas, sed ne le fait pas).

Autant que je sache, il n’y a pas d’utilitaire Unix “standard” capable de faire cela, et ce n’est pas très facile à faire avec un shell unique, donc un script est nécessaire.

Il y a quelque temps, j’ai créé un petit script appelé sanitize_files qui fait exactement cela. Il corrige également d’autres choses courantes comme le remplacement de \r\n par \n , l’ajout d’un \n traînant, etc.

Vous pouvez trouver un script simplifié sans les fonctionnalités supplémentaires et les arguments de ligne de commande ci-dessous, mais je vous recommande d’utiliser le script ci-dessus car il est plus susceptible de recevoir des corrections de bogues et d’autres mises à jour que ce post.

Je voudrais également souligner, en réponse à certaines des autres réponses ici, que l’utilisation de la globalisation de shell n’est pas un moyen robuste de le faire, car tôt ou tard vous vous retrouverez avec plus de fichiers que ce qui est possible dans ARG_MAX (on systèmes Linux modernes, il est 128k, ce qui peut sembler beaucoup, mais tôt ou tard ce n’est pas suffisant).


 #!/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) 

Vous pouvez utiliser la commande pr généralement disponible (page de manuel ici ). Par exemple, pour convertir des tabs en quatre espaces, procédez comme suit:

 pr -t -e=4 file > file.expanded 
  • -t supprime les en-têtes
  • -e=num étend les tabs aux espaces num

Pour convertir tous les fichiers d’une arborescence de répertoires de manière récursive, en ignorant les fichiers binarys:

 #!/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 logique pour ignorer les fichiers binarys provient de cette publication .

REMARQUE:

  1. Faire cela pourrait être dangereux dans un repo git ou svn
  2. Ce n’est pas la bonne solution si vous disposez de fichiers de code comportant des tabs incorporés dans des littéraux de chaîne.

J’ai utilisé astyle pour recréer tout mon code C / C ++ après avoir trouvé des tabs et des espaces mixtes. Il a également des options pour forcer un style particulier si vous le souhaitez.

Ma recommandation est d’utiliser:

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

Commentaires:

  1. Utiliser l’édition en place. Conservez des sauvegardes dans un VCS. Pas besoin de produire des fichiers * .orig. Il est recommandé de comparer le résultat à votre dernier engagement pour vous assurer que cela fonctionne comme prévu, dans tous les cas.
  2. sed est un éditeur de stream. Utilisez ex pour l’édition en place. Cela évite de créer des fichiers temporaires supplémentaires et de générer des shells pour chaque remplacement, comme dans la réponse supérieure .
  3. AVERTISSEMENT: Cela perturbe tous les tabs, pas seulement ceux utilisés pour l’indentation. En outre, il ne fait pas de remplacement contextuel des tabs. C’était suffisant pour mon cas d’utilisation. Mais pourrait ne pas être acceptable pour vous.
  4. EDIT: Une version antérieure de cette réponse utilisait find|xargs au lieu de find -exec . Comme le fait remarquer @gniourf-gniourf, cela conduit à des problèmes d’espaces, de guillemets et de caractères de contrôle dans les noms de fichiers, cf. Wheeler .

Pour convertir tous les fichiers Java de manière récursive dans un répertoire pour utiliser 4 espaces au lieu d’un onglet:

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

Téléchargez et exécutez le script suivant pour convertir récursivement les tabs durs en tabs logiciels dans des fichiers en texte brut.

Exécutez le script depuis le dossier contenant les fichiers texte brut.

 #!/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; 

On peut utiliser vim pour cela:

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

Comme l’a déclaré Carpetsmoker, il sera modifié en fonction de vos parameters vim . Et les modèles dans les fichiers, le cas échéant. En outre, il remplacera les tabs non seulement au début des lignes. Ce qui n’est pas ce que vous voulez généralement. Par exemple, vous pourriez avoir des littéraux, contenant des tabulations.

Vous pouvez utiliser find avec le package tabs-to-spaces pour cela.

Tout d’abord, installez les tabs-to-spaces

 npm install -g tabs-to-spaces 

ensuite, exécutez cette commande à partir du répertoire racine de votre projet;

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

Cela remplacera chaque caractère de tab par 2 spaces dans chaque fichier.

Conversion d’tabs en espace dans les fichiers “.lua” [tabs -> 2 espaces]

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

Utilisez le vim-way:

 $ ex +'bufdo retab' -cxa **/*.* 
  • Faites la sauvegarde! avant d’exécuter la commande ci-dessus, car elle peut corrompre vos fichiers binarys.
  • Pour utiliser globstar ( ** ) pour la récursivité, activez-le par shopt -s globstar .
  • Pour spécifier un type de fichier spécifique, utilisez par exemple: **/*.c

Pour modifier le tabstop, ajoutez +'set ts=2' .

Cependant, l’inconvénient est qu’il peut remplacer les tabs à l’intérieur des chaînes .

Donc, pour une solution légèrement meilleure (en utilisant la substitution), essayez:

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

Ou en utilisant ex editor + expand utilitaire:

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

Pour les espaces de fin, voir: Comment supprimer les espaces de fin de fichier pour plusieurs fichiers?


Vous pouvez append la fonction suivante à votre .bash_profile :

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

L’utilisation de l’ expand comme suggéré dans d’autres réponses semble l’approche la plus logique pour cette seule tâche.

Cela dit, cela peut aussi être fait avec Bash et Awk au cas où vous souhaiteriez apporter d’autres modifications.

Si vous utilisez Bash 4.0 ou une version ultérieure , le globstar intégré de globstar peut être utilisé pour rechercher de manière récursive avec ** .

Avec GNU Awk version 4.1 ou supérieure, des modifications de fichiers sed comme “inplace” peuvent être effectuées:

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

Si vous souhaitez définir le nombre d’espaces par onglet:

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