for - bash script



Como converter conversas em espaços em todos os arquivos de um diretório? (13)

Aviso: Isso irá quebrar seu repo.

Isso corromperá os arquivos binários , incluindo os que .git sob svn , .git ! Leia os comentários antes de usar!

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

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

Desvantagens:

  • Substituirá as guias por todos os lugares em um arquivo.
  • Levará muito tempo se você tiver um dump SQL de 5 GB nesse diretório.

Como converter conversas 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?


Answer #1

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

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

Você quer fazer isso para imagens png? Arquivos PDF? O diretório .git? Seu Makefile (que requer abas)? Um despejo SQL de 5GB?

Você poderia, em teoria, passar um monte de opções de exlude para find ou qualquer outra coisa que você esteja usando; mas isso é frágil e irá quebrar assim que você adicionar outros arquivos binários.

O que você quer, é pelo menos:

  1. Ignore os arquivos em um determinado tamanho.
  2. Detectar se um arquivo é binário verificando a presença de um byte NULL.
  3. Apenas substitua as guias no início de um arquivo ( expand , o sed não faz isso).

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

Um tempo atrás eu criei um pequeno script chamado sanitize_files que faz exatamente isso. Ele também corrige algumas outras coisas comuns, como substituir \r\n por \n , adicionando um \n arrastado, etc.

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

Eu também gostaria de salientar, em resposta a algumas das outras respostas aqui, que usar o shell globbing não é uma maneira robusta de fazer isso, porque mais cedo ou mais tarde você vai acabar com mais arquivos do que o ARG_MAX (em sistemas Linux modernos são 128k, o 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)

Answer #2

Converter guias no espaço apenas em arquivos ".lua" [tabs -> 2 spaces]

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

Answer #3

Eu gosto do exemplo "find" acima para o aplicativo recursivo. Para adaptá-lo para não ser recursivo, apenas alterando os arquivos no diretório atual que correspondem a um caractere curinga, a expansão do glob globular 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ê quer que ele fique em silêncio depois de confiar que funciona, simplesmente solte o -v no comando sh no final.

Claro que você pode escolher qualquer conjunto de arquivos no primeiro comando. Por exemplo, liste apenas um subdiretório específico (ou diretórios) 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 o 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

Answer #4

Faça o download e execute o seguinte script para converter recursivamente guias difíceis em guias simples em arquivos de texto simples.

Execute o script de dentro da pasta que contém os arquivos de texto simples.

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

Answer #5

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 para diferenciar o resultado do último commit para garantir que isso funcione como esperado, em qualquer caso.
  2. sed é um editor de fluxo. Use ex para edição em vigor. Isso evita a criação de arquivos temporários extras e shells de desova para cada substituição, como na resposta principal .
  3. AVISO: Isso mexe com todas as guias, não apenas aquelas usadas para recuo. Também não faz a substituição consciente de contexto das abas. 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 usou find|xargs vez de find -exec . Como apontado por @ gniourf-gniourf isso leva a problemas com espaços, citações e caracteres de controle em nomes de arquivos cf. Wheeler

Answer #6

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

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

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

Com o GNU Awk versão 4.1 ou superior, sed como modificações no arquivo "inplace" podem ser feitas:

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

Answer #7

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

Answer #8

Substituição simples com sed está bem, 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, de modo que as margens ficarão irregulares. Guias expandidas no meio das linhas também não funcionarão corretamente. Na 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 expand a cada arquivo Java na árvore de diretórios atual. Remova / substitua o argumento -name se você estiver segmentando alguns outros tipos de arquivo. Como um dos comentários menciona, tenha muito cuidado ao remover -name ou usando um caractere curinga fraco. Você pode facilmente destruir o repositório e outros arquivos ocultos sem intenção. É por isso que a resposta original incluiu isto:

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


Answer #9

Tente a ferramenta de linha de comando expand .

expand -i -t 4 input | sponge output

Onde

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

Finalmente, você pode usar o gexpand no OSX, depois de instalar o coreutils com o Homebrew ( brew install coreutils ).


Answer #10

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 um tipo de arquivo específico, use por exemplo: **/*.c

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

No entanto, o lado negativo é que ele pode substituir guias dentro das seqüências de caracteres .

Então, para uma solução ligeiramente melhor (usando substituição), tente:

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

Ou usando o ex editor + expand utilitário:

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

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

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

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

Answer #11

Você pode usar o find com o pacote tabs-to-spaces para isso.

Primeiro, instale as tabs-to-spaces

npm install -g tabs-to-spaces

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

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

Isso substituirá todos os caracteres de tab por dois spaces em cada arquivo.


Answer #12

Método amigável do 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 sob o diretório atual:

git-tab-to-space

Atua somente em arquivos C ou C ++:

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

Você provavelmente quer isto notavelmente por causa daqueles Makefiles irritantes que requerem abas.

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

  • lista apenas os arquivos rastreados, então nada dentro do .git
  • exclui diretórios, arquivos binários (seriam corrompidos) 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 --reference mantém as permissões de arquivo inalteradas: https://unix.stackexchange.com/questions/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 idéia maluca de permitir guias brutas funcionais em strings, use:

expand -i

e então divirta-se passando por cima de todas as abas não iniciais de linha uma a uma, com as quais você pode listar: É possível git grep para abas?

Testado no Ubuntu 18.04.





in-place