web-dev-qa-db-fra.com

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

Comment convertir des tabulations en espaces dans chaque fichier d'un répertoire (éventuellement de manière récursive)?

De même, existe-t-il un moyen de définir le nombre d'espaces par onglet?

213
Cynede

Attention: cela va casser votre repo.

Ceci corrompra les fichiers binaires , y compris ceux de 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 nom [filename].orig.

Inconvénients:

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

Un remplacement simple avec sed est correct mais pas la meilleure solution possible. S'il y a des "extra" espaces entre les onglets, ils seront toujours là après la substitution, les marges seront alors déchiquetées. Les onglets développés au milieu des lignes ne fonctionneront pas non plus correctement. Dans 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 expand à chaque fichier Java de l’arborescence de répertoires actuelle. 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 clobber le référentiel et d'autres fichiers cachés sans intention. C'est pourquoi la réponse originale incluait ceci:

Vous devriez toujours faire une copie de sauvegarde de l’arbre avant d’essayer quelque chose comme cela au cas où quelque chose se passerait mal.

316
Gene

Essayez l'outil de ligne de commande expand .

expand -i -t 4 input | sponge output

  • -i est utilisé pour développer uniquement les onglets de tête sur chaque ligne;
  • -t 4 signifie que chaque onglet sera converti en 4 caractères d'espacement (8 par défaut).
  • sponge est issu du moreutils package et évite l'effacement du fichier d'entrée .

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

174
kev

Recueillir les meilleurs commentaires de La réponse de Gene , la meilleure solution de loin, consiste à utiliser 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: 

  • ./ cherche récursivement dans le répertoire en cours
  • -iname est une correspondance insensible à la casse (pour les goûts *.Java et *.Java)
  • type -f ne trouve que des fichiers normaux (pas de répertoires, binaires ou liens symboliques)
  • -exec bash -c exécute les commandes suivantes dans un sous-shell pour chaque nom de fichier, {}
  • expand -t 4 étend tous les onglets à 4 espaces
  • sponge absorber les entrées standard (à partir de expand) et écrire dans un fichier (le même) *. 

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

Advantage: toutes les autorisations de fichier d'origine sont conservées et aucun fichier tmp intermédiaire n'est utilisé.

16
not2qubit

Utilisez sed avec une barre oblique inversée.

Sur linux:

  • Remplacez tous les onglets par 1 trait d'union, dans tous les fichiers * .txt:

    sed -i $'s/\t/-/g' *.txt
    
  • Remplacez tous les onglets par 1 espace inséré, dans tous les fichiers * .txt:

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

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

Sur un mac:

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

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

J'aime l'exemple de "trouver" ci-dessus pour l'application récursive. Pour l'adapter de manière non récursive, en modifiant uniquement les fichiers du répertoire en cours qui correspondent à un caractère générique, l'extension globale du shell peut être suffisante 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 souhaitez le garder silencieux une fois que vous avez confiance que cela fonctionne, supprimez simplement le -v sur la commande sh à la fin.

Bien sûr, vous pouvez choisir n’importe quel ensemble de fichiers dans la première commande. Par exemple, répertoriez uniquement un sous-répertoire particulier (ou des répertoires) 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 alors exécutez find (1) avec une combinaison de paramètres de profondeur, etc.:

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

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

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

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

#!/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 binaires provient de this post .

REMARQUE:

  1. Cela pourrait être dangereux dans un repo git ou svn
  2. Ce n'est pas la bonne solution si vous avez des fichiers de code avec des onglets incorporés dans des littéraux de chaîne
3
codeforester

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

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

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

Vous pouvez, en théorie, passer beaucoup d'options d'exlude à find ou à quoi que ce soit d'autre Que vous utilisez; mais ceci est fragile et se cassera dès que vous ajoutez d'autres fichiers binaires

Ce que vous voulez, c'est au moins:

  1. Ignorer les fichiers d'une certaine taille.
  2. Détecter si un fichier est binaire en vérifiant la présence d'un octet NULL.
  3. Ne remplacez les tabulations qu'au début d'un fichier (expand le fait, sed Ne le fait pas).

Autant que je sache, il n’existe pas d’utilitaire Unix "standard" capable de le faire, et ce n’est pas très facile à faire avec une ligne de commande Shell, un script est donc 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 problèmes courants, tels que le remplacement de \r\n par \n, En ajoutant un \n final, 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 bugs et Autres mises à jour que ce message. .

Je voudrais également souligner, en réponse à certaines des réponses données ici: Que l'utilisation de Shell globbing n'est pas une manière robuste de le faire, car plus tôt ou plus tard vous finirez par vous retrouver avec plus de fichiers que le contenu de ARG_MAX (sur les systèmes modernes Linux, il s’agit de 128 Ko, ce qui peut sembler beaucoup, mais tôt ou tard, il suffira de pas.__).


#!/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 __== '__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)
3
Martin Tournoij

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

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

Téléchargez et exécutez le script suivant pour convertir de manière récursive des onglets fixes en onglets souples dans des fichiers texte.

Exécutez le script depuis l'intérieur du dossier contenant les fichiers de 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;
1
daka

Pas de corps mentionné rpl? En utilisant rpl, vous pouvez remplacer n’importe quelle chaîne. Pour convertir des onglets en espaces,

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

très simple.

1

J'ai utilisé astyle pour réindenter tout mon code C/C++ après avoir trouvé des tabulations et des espaces mélangés. Il a également des options pour forcer un style d'accolade particulier si vous le souhaitez.

1
Theo Belaire

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

Cela dit, cela peut également être fait avec Bash et Awk au cas où vous voudriez faire d'autres modifications avec.

Si vous utilisez Bash 4.0 ou une version ultérieure, utilisez shopt builtinglobstar pour rechercher récursivement avec **.

Avec GNU Awk version 4.1 ou supérieure, le fichier "inplace" peut être modifié:

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

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

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

Ma recommandation est d'utiliser:

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

Commentaires:

  1. Utilisez la modification sur place. Gardez les sauvegardes dans un VCS. Pas besoin de produire des fichiers * .orig. Il est bon de comparer le résultat à votre dernier engagement pour vous assurer que cela a fonctionné comme prévu, dans tous les cas.
  2. sed est un éditeur de flux. Utilisez ex pour la modification sur place. Cela évite de créer des fichiers temporaires supplémentaires et de générer des shells pour chaque remplacement, comme indiqué dans le réponse du haut
  3. AVERTISSEMENT: Cela concerne tous les onglets, pas seulement ceux utilisés pour l'indentation. En outre, il ne fait pas de remplacement contextuel des onglets. 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 l'a souligné @ gniourf-gniourf, cela entraîne des problèmes d'espaces, de guillemets et de caractères de contrôle dans les noms de fichiers, cf. Wheeler .
1
Heinrich Hartmann

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’indique Carpetsmoker, les modifications seront effectuées selon vos paramètres vim. Et des modelines dans les fichiers, le cas échéant. En outre, il remplacera les tabulations 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 onglets.

0
x-yuri

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

Tout d'abord, installez 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 tab par 2 spaces dans chaque fichier.

0
Harsh Vakharia

Méthode conviviale pour le dépôt 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"
)

Agit sur tous les fichiers du répertoire actuel:

git-tab-to-space

Agissez uniquement sur les fichiers C ou C++:

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

Vous voulez probablement cela, notamment à cause de ces Makefiles agaçants qui nécessitent des onglets.

La commande git grep --cached -Il '':

  • répertorie uniquement les fichiers suivis, donc rien à l'intérieur de .git
  • exclut les répertoires, les fichiers binaires (qui seraient corrompus) et les liens symboliques (seraient convertis en fichiers normaux)

comme expliqué à: Comment lister tous les fichiers texte (non binaires) dans un référentiel git?

chmod --reference conserve les autorisations de fichiers inchangées: https://unix.stackexchange.com/questions/20645/clone-ownership-and-permissions-from-another-file Malheureusement, je impossible de trouver un alternative POSIX succincte .

Si votre base de code a eu l’idée folle d’autoriser les onglets bruts fonctionnels dans les chaînes, utilisez:

expand -i

et amusez-vous ensuite en parcourant un par un tous les onglets autres que le début de la ligne, que vous pouvez répertorier avec: Est-il possible de git grep pour les onglets?

Testé sur Ubuntu 18.04.