Como posso converter guias em espaços em todos os arquivos de um diretório?

251

Como posso converter guias em espaços em todos os arquivos de um diretório (possivelmente recursivamente)?

Além disso, existe uma maneira de definir o número de espaços por guia?

cnd
fonte
Deseja substituir guias em arquivos ou nomes de arquivos?
Cppcoder
3
pré uma utilidade maravilhosa para isso. Veja esta resposta .
precisa saber é o seguinte

Respostas:

69

Aviso: Isso interromperá seu repo.

Esta vontade arquivos binários corruptos , incluindo aqueles sob svn, .git! Leia os comentários antes de usar!

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

O arquivo original é salvo como [filename].orig.

Substitua '* .java' pelo final do arquivo que você está procurando. Dessa forma, você pode impedir a corrupção acidental de arquivos binários.

Desvantagens:

  • Substituirá as guias em todos os lugares do arquivo.
  • Levará muito tempo se houver um despejo de SQL de 5 GB neste diretório.
Martin Beckett
fonte
12
para o espaço visual que é uma mistura de guias e espaços, essa abordagem fornece expansão incorreta.
pizza
7
Eu também adicionaria um comparador de arquivos como, por exemplo, apenas para arquivos .php encontrar ./-nome "* .php" -tipo f -exec sed -i 's / \ t / / g' {} \;
Daniel Luca CleanUnicorn
98
NÃO USE SED! Se houver uma guia incorporada em uma string, você pode acabar manipulando seu código. É com isso que o comando de expansão foi criado. Use expand.
David W.
5
@DavidW. Eu simplesmente atualizaria este comando para substituir apenas as guias desde o início da linha. find ./ -type f -exec sed -i 's/^\t/####/g' {} \;. Mas eu não estava ciente do comando de expansão - muito útil!
Martin Konecny
29
NÃO USE! Esta resposta também acabou com o meu repositório git local. Se você tiver arquivos contendo guias e espaços misturados, ele inserirá sequências de # 's. Use a resposta de Gene ou o comentário de Doge abaixo.
fantoche
344

A substituição simples por sedestá correta, mas não é a melhor solução possível. Se houver espaços "extras" entre as guias, eles ainda estarão lá após a substituição, portanto as margens serão irregulares. Guias expandidas no meio das linhas também não funcionarão corretamente. Em bash, podemos dizer em vez disso

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

para aplicar expanda todos os arquivos Java na árvore de diretórios atual. Remova / substitua o -nameargumento se você estiver direcionando outros tipos de arquivo. Como um dos comentários menciona, tenha muito cuidado ao remover -nameou usar um caractere curinga fraco. Você pode facilmente recuperar repositórios e outros arquivos ocultos sem intenção. É por isso que a resposta original incluiu isso:

Você sempre deve fazer uma cópia de segurança da árvore antes de tentar algo assim, caso algo dê errado.

Gene
fonte
2
@JeffreyMartinez Great question. gniourf_gniourf editou minha resposta original em 11 de novembro e fez comentários depreciativos sobre não saber a maneira correta de usar {}. Parece que ele não sabia $0quando -cé usado. Em seguida, o dimo414 mudou do meu uso de um temp no diretório de conversão para /tmp, que será muito mais lento se /tmpestiver em um ponto de montagem diferente. Infelizmente, não tenho uma caixa Linux disponível para testar sua $0proposta. Mas acho que você está correto.
Gene
1
@Gene, obrigado pelo esclarecimento, isso soa como um stackoverflow certo: p. Enquanto estou no assunto, acrescentarei que tive que usar aspas em torno de '* .java' para escapar adequadamente do * .java.
Jeffrey Martinez
2
Se alguém está a ter um erro 'desconhecido primária ou operador' do achado, então aqui é o comando completo que irá corrigi-lo:find . -name '*.java' ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;
Doge
4
Eu pensei que esta resposta não tinha comentários suficientes como era, por isso esta é minha: se o uso de uso spongede joeyh.name/code/moreutils , você pode escreverfind . -name '*.py' ! -type d -exec bash -c 'expand -t 8 "$0" | sponge "$0"' {} \;
tokland
8
Não seja estúpido e uso find . -name '*', eu só destruiu o meu local repo git
Gautam
193

Experimente a ferramenta de linha de comando expand.

expand -i -t 4 input | sponge output

Onde

  • -i é usado para expandir apenas as guias principais em cada linha;
  • -t 4 significa que cada guia será convertida em 4 caracteres de espaço em branco (8 por padrão).
  • spongeé do moreutilspacote e evita limpar o arquivo de entrada .

Finalmente, você pode usar gexpandno OSX, depois de instalar coreutilscom o Homebrew ( brew install coreutils).

kev
fonte
5
É um dos GNU_Core_Utilities
kev
32
Você deve passar -ipara expandpara substituir apenas as guias principais em cada linha. Isso ajuda a evitar a substituição de guias que podem fazer parte do código.
Perguntas Quolonel
10
que tal recursivamente para cada arquivo em um diretório?
ahnbizcad
4
Toda vez que tento usar isso, apaga alguns (geralmente todos) dos arquivos. : \
ThorSummoner
5
@ ThorSummoner: se inputé o mesmo arquivo que outputo bash clobbers o conteúdo antes mesmo de começar expand. É assim que >funciona.
Robert Siemer
34

Coletando os melhores comentários de resposta de Gene , a melhor solução, de longe, é usando spongede moreutils .

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

Explicação:

  • ./ está pesquisando recursivamente a partir do diretório atual
  • -inameé uma correspondência que não diferencia maiúsculas de minúsculas (para ambos *.javae *.JAVAgostos)
  • type -f localiza apenas arquivos regulares (sem diretórios, binários ou links simbólicos)
  • -exec bash -c execute os seguintes comandos em uma subshell para cada nome de arquivo, {}
  • expand -t 4 expande todos os TABs para 4 espaços
  • spongeabsorva a entrada padrão (de expand) e grave em um arquivo (o mesmo) *.

NOTA : * Um simples redirecionamento de arquivo ( > "$0") não funcionará aqui porque substituirá o arquivo muito cedo .

Vantagem : Todas as permissões de arquivo originais são mantidas e nenhum tmparquivo intermediário é usado.

not2qubit
fonte
2
TIL: o maravilhoso comando de esponja, após 15 anos de uso do Linux. Obrigado cavaleiro misterioso da internet.
Sscarduzio
19

Use escape com barra invertida sed.

No linux:

  • Substitua todas as guias por 1 hífen no local, em todos os arquivos * .txt:

    sed -i $'s/\t/-/g' *.txt
  • Substitua todas as guias por 1 espaço no local, em todos os arquivos * .txt:

    sed -i $'s/\t/ /g' *.txt
  • Substitua todas as guias com 4 espaços no local, em todos os arquivos * .txt:

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

Em um mac:

  • Substitua todas as guias com 4 espaços no local, em todos os arquivos * .txt:

    sed -i '' $'s/\t/    /g' *.txt
e9t
fonte
2
@ Машаsed -i '' $'s/\t/ /g' $(find . -name "*.txt")
xyzale
Esta resposta parece ser a mais simples.
Yan King Yin
6

Você pode usar o prcomando geralmente disponível (página de manual aqui ). Por exemplo, para converter guias em quatro espaços, faça o seguinte:

pr -t -e=4 file > file.expanded
  • -t suprime cabeçalhos
  • -e=numexpande guias para numespaços

Para converter todos os arquivos em uma árvore de diretórios recursivamente, ignorando arquivos binários:

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

A lógica para pular arquivos binários é desta postagem .

NOTA:

  1. Fazer isso pode ser perigoso em um repositório git ou svn
  2. Esta não é a solução certa se você tiver arquivos de código com guias incorporadas em literais de string
codeforester
fonte
1
Alguma vantagem, expanddado que ambos são POSIX? Por exemplo, tem uma opção de alteração em linha? Git
security
5

Como posso converter guias em espaços em todos os arquivos de um diretório (possivelmente recursivamente)?

Isso geralmente não é o que você deseja.

Deseja fazer isso para imagens png? Arquivos PDF? O diretório .git? Seu Makefile(que requer guias)? Um dump SQL de 5 GB?

Você poderia, em teoria, passar muitas opções de exclusão para findou o que quer que esteja usando; mas isso é frágil e será interrompido assim que você adicionar outros arquivos binários.

O que você quer é pelo menos:

  1. Pule arquivos acima de um determinado tamanho.
  2. Detecte se um arquivo é binário, verificando a presença de um byte NULL.
  3. Substitua apenas as guias no início de um arquivo ( expandisso sed não ocorre).

Até onde eu sei, não existe um utilitário Unix "padrão" que possa fazer isso, e não é muito fácil fazer isso com um liner de shell, portanto é necessário um script.

Há um tempo atrás, criei um pequeno script chamado sanitize_files, que faz exatamente isso. Ele também corrige outras coisas comuns, como substituir \r\npor \n, adicionar um final \n, etc.

Você pode encontrar um script simplificado sem os recursos extras e os argumentos da linha de comando abaixo, mas eu recomendo que você use o script acima, pois é mais provável que você receba correções de bugs e outras atualizações atualizadas além desta publicação.

Eu também gostaria de salientar, em resposta a algumas das outras respostas aqui, que o uso de globbing de shell não é uma maneira robusta de fazer isso, porque mais cedo ou mais tarde você terá mais arquivos do que o necessário ARG_MAX(nos modernos sistemas Linux é 128k, que pode parecer muito, mas mais cedo ou mais tarde, é não 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)
Martin Tournoij
fonte
Dentro do git, a verificação binária é fácil: stackoverflow.com/a/52136507/895245
Ciro Santilli = (
5

Eu gosto do exemplo "find" acima para o aplicativo recursivo. Para adaptá-lo para não ser recursivo, alterando apenas os arquivos no diretório atual que correspondem a um curinga, a expansão do shell glob pode ser suficiente para pequenas quantidades de arquivos:

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

Se você deseja silenciá-lo depois de confiar que ele funciona, basta soltar -vo shcomando no final.

Claro que você pode escolher qualquer conjunto de arquivos no primeiro comando. Por exemplo, liste apenas um subdiretório (ou diretórios) específico de uma maneira controlada como esta:

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

Ou, por sua vez, execute find (1) com alguma combinação de parâmetros de profundidade, etc:

find mod/ -name '*.php' -mindepth 1 -maxdepth 2 | awk '{print "expand -t 4 ", $0, " > /tmp/e; mv /tmp/e ", $0}' | sh
drchuck
fonte
1
O globbing do shell quebrará mais cedo ou mais tarde, porque a quantidade total de nomes de arquivos pode ter apenas o ARG_MAXcomprimento. Isso é 128k em sistemas Linux, mas eu encontrei esse limite o tempo suficiente para não confiar no globbing do shell.
Martin Tournoij 12/08/2015
1
Você realmente não precisa adaptá-los. findpode ser informado -maxdepth 1e processa apenas as entradas do diretório que está sendo modificado, não a árvore inteira.
ShadowRanger
4

Eu costumava astylereentrar todo o meu código C / C ++ depois de encontrar guias e espaços mistos. Ele também possui opções para forçar um estilo de chave específico, se você desejar.

Theo Belaire
fonte
4

Pode-se usar vimpara isso:

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

Como o Carpetsmoker declarou, ele fará uma nova revisão de acordo com vim configurações. E modelos nos arquivos, se houver. Além disso, substituirá as guias não apenas no início das linhas. O que não é o que você geralmente deseja. Por exemplo, você pode ter literais, contendo guias.

x-yuri
fonte
:retabalterará todas as guias de um arquivo, não aquelas no início. também depende de quais são suas configurações :tabstope :expandtabno vimrc ou modeline, portanto, isso pode não funcionar.
Martin Tournoij 12/08/2015
@Carpetsmoker Bom argumento sobre as guias no início das linhas. Alguma das soluções aqui lida com esse caso? Quanto às configurações tabstope expandtab, funcionará se você estiver usando vim. A menos que você tenha linhas de modo nos arquivos.
X-yuri
@ x-yuri boa pergunta, mas geralmente discutível. A maioria das pessoas usa guias não reais em literais.
Ricardo Cruz
4

Minha recomendação é usar:

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

Comentários:

  1. Use a edição no local. Mantenha backups em um VCS. Não há necessidade de produzir arquivos * .orig. É uma boa prática diferenciar o resultado do seu último commit para garantir que funcione conforme o esperado, em qualquer caso.
  2. sedé um editor de stream. Use expara edição no local. Isso evita a criação de arquivos temporários extras e conchas de desova para cada substituição, como na resposta superior .
  3. AVISO: Isso interfere em todas as guias, não apenas nas usadas para recuo. Além disso, ele não substitui as guias com reconhecimento de contexto. Isso foi suficiente para o meu caso de uso. Mas pode não ser aceitável para você.
  4. EDIT: Uma versão anterior desta resposta usada em find|xargsvez de find -exec. Como apontado por @ gniourf-gniourf, isso leva a problemas com espaços, aspas e caracteres de controle nos nomes de arquivos, cf. Wheeler .
Heinrich Hartmann
fonte
expode não estar disponível em todos os sistemas Unix. Substituí-lo por vi -epode funcionar em mais máquinas. Além disso, seu regex substitui qualquer número de caracteres da guia inicial por dois espaços. Substitua o regex por +%s/\t/ /gnão destruir o recuo de vários níveis. No entanto, isso também afeta os caracteres de tabulação que não são usados ​​para indentação.
Lukas Schmelzeisen
ex faz parte do POSIX [1], portanto deve estar disponível. Bom ponto sobre indendação de vários níveis. Na verdade, eu havia usado a /\t/ /variante em meus arquivos, mas optei por /\t\+//não quebrar guias não recuadas. Perdeu os problemas com a multi-indentação! Atualizando a resposta. [1] man7.org/linux/man-pages/man1/ex.1p.html#SEE%C2%A0ALSO
Heinrich Hartmann
2
Usar xargsdessa maneira é inútil, ineficiente e quebrado (pense em nomes de arquivos que contenham espaços ou aspas). Por que você não usa findo -execcomutador?
gniourf_gniourf
Eu diria que nomes de arquivos com espaços e aspas estão quebrados; ) Se você precisar apoiar, eu optaria por: -print0opções para encontrar / xargs. Gosto do xargs -execdesde: a) Separação de preocupações b) pode ser trocado com o GNU paralelo mais facilmente.
Heinrich Hartmann
Atualizado adicionando comentários @gniourf_gniourf.
Heinrich Hartmann
4

Para converter todos os arquivos Java recursivamente em um diretório para usar 4 espaços em vez de uma guia:

find . -type f -name *.java -exec bash -c 'expand -t 4 {} > /tmp/stuff;mv /tmp/stuff {}' \;
Raffi Khatchadourian
fonte
Como isso é diferente resposta de esta que foi publicado há 4 anos?
PP
2
O mesmo acontece com a sua resposta. De fato, esta é uma versão inferior da resposta de Gene: 1) A resposta de Gene cuida de diretórios com o mesmo nome. 2) Não se move se a expansão falhar.
PP
4

Você pode usar findcom o tabs-to-spacespacote para isso.

Primeiro, instale tabs-to-spaces

npm install -g tabs-to-spaces

em seguida, execute este comando no diretório raiz do seu projeto;

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

Isso substituirá cada tabcaractere por 2 spacesem cada arquivo.

Harsh Vakharia
fonte
3

Nenhum corpo mencionado rpl? Usando o rpl, você pode substituir qualquer string. Para converter guias em espaços,

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

muito simples.

PessoasMontarPessoasMar
fonte
1
Isso corrompeu todos os arquivos binários no meu repositório.
Aaron Franke
1
Um comando excelente, mas potencialmente perigoso com a opção recursiva e todos os arquivos na pasta, conforme especificado acima. Eu adicionaria a opção --dry-run "apenas no caso" para garantir que você esteja sentado na pasta correta.
MortimerCat
2

O uso de expand como sugerido em outras respostas parece a abordagem mais lógica para esta tarefa sozinha.

Dito isto, também pode ser feito com o Bash e o Awk, caso você queira fazer outras modificações.

Se estiver usando o Bash 4.0 ou superior, o shopt interno globstar pode ser usado para pesquisar recursivamente **.

Com o GNU Awk versão 4.1 ou superior, é possível fazer modificações no arquivo "inplace":

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

Caso você queira definir o número de espaços por guia:

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

Faça o download e execute o script a seguir para converter recursivamente guias rígidas em guias flexíveis em arquivos de texto sem formatação.

Execute o script de dentro da pasta que contém os arquivos de texto sem formatação.

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

Método amigável ao repositório 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"
)

Atue em todos os arquivos no diretório atual:

git-tab-to-space

Atue apenas em arquivos C ou C ++:

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

Você provavelmente deseja isso principalmente por causa dos Makefiles irritantes que exigem guias.

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

  • lista apenas os arquivos rastreados, então nada dentro .git
  • exclui diretórios, arquivos binários (seria corrompido) e links simbólicos (seriam convertidos em arquivos regulares)

como explicado em: Como listar todos os arquivos de texto (não binários) em um repositório git?

chmod --referencemantém as permissões de arquivo inalteradas: /unix/20645/clone-ownership-and-permissions-from-another-file Infelizmente, não consigo encontrar uma alternativa POSIX sucinta .

Se sua base de código teve a ideia maluca de permitir guias funcionais em strings, use:

expand -i

e divirta-se examinando todas as guias que não são do início da linha, uma a uma, com as quais você pode listar: É possível obter o grep para as guias?

Testado no Ubuntu 18.04.

Ciro Santilli adicionou uma nova foto
fonte
-1

Convertendo guias em espaço apenas em arquivos ".lua" [guias -> 2 espaços]

find . -iname "*.lua" -exec sed -i "s#\t#  #g" '{}' \;
Makah
fonte
Obviamente, a quantidade de espaço que uma guia se expande depende do contexto. Assim, sed é uma ferramenta completamente inadequada para a tarefa.
Sven
?? @Sven, meu comando sed faz a mesma coisa que o comando expand ( expand -t 4 input >output)
Makah
3
Claro que não. expand -t 4expandirá a guia em a\tb3 espaços e a guia em aa\tb2 espaços, exatamente como deveria ser. expandleva em consideração o contexto de uma guia, sednão substitui e substituirá a guia pela quantidade de espaços que você especificar, independentemente do contexto.
Sven
-1

Use o vim-way:

$ ex +'bufdo retab' -cxa **/*.*
  • Faça o backup! antes de executar o comando acima, pois ele pode corromper seus arquivos binários.
  • Para usar globstar( **) para recursão, ative por shopt -s globstar.
  • Para especificar o tipo de arquivo específico, use por exemplo: **/*.c.

Para modificar o tabstop, adicione +'set ts=2'.

No entanto, o lado negativo é que ele pode substituir as guias dentro das strings .

Portanto, para uma solução um pouco melhor (usando substituição), tente:

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

Ou usando o exeditor + expandutilitário:

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

Para espaços à direita, consulte: Como remover espaços em branco à direita para vários arquivos?


Você pode adicionar a seguinte função ao seu .bash_profile:

# Convert tabs to spaces.
# Usage: retab *.*
# See: https://stackoverflow.com/q/11094383/55075
retab() {
  ex +'set ts=2' +'bufdo retab' -cxa $*
}
kenorb
fonte
Eu diminuí o voto de muitas respostas neste tópico, não apenas o seu ;-) Os motivos são: :retabpode não funcionar , shell globbing é uma solução ruim para esse tipo de coisa , seu :scomando substituirá qualquer quantidade de guias por 2 espaços (que você quase nunca quero), começando ex só para executar um :!expandprocesso é bobagem ...
Martin Tournoij
... e todas as suas soluções vai espancar arquivos binários e tal (como arquivos .png, arquivos .pdf, etc.)
Martin Tournoij
Essa é francamente uma sugestão horrível para a documentação - é preciso familiarizar-se intimamente com uma série de questões bastante opacas de sintaxe e semântica de vários programas para poder entender isso.
Josip Rodin