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?
pr
é uma utilidade maravilhosa para isso. Veja esta resposta .
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?
pr
é uma utilidade maravilhosa para isso. Veja esta resposta .
Respostas:
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:
find ./ -type f -exec sed -i 's/^\t/####/g' {} \;
. Mas eu não estava ciente do comando de expansão - muito útil!
A substituição simples por sed
está 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 expand
a todos os arquivos Java na árvore de diretórios atual. Remova / substitua o -name
argumento se você estiver direcionando outros tipos de arquivo. Como um dos comentários menciona, tenha muito cuidado ao remover -name
ou 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.
{}
. Parece que ele não sabia $0
quando -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 /tmp
estiver em um ponto de montagem diferente. Infelizmente, não tenho uma caixa Linux disponível para testar sua $0
proposta. Mas acho que você está correto.
find . -name '*.java' ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;
sponge
de joeyh.name/code/moreutils , você pode escreverfind . -name '*.py' ! -type d -exec bash -c 'expand -t 8 "$0" | sponge "$0"' {} \;
find . -name '*'
, eu só destruiu o meu local repo git
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 moreutils
pacote e evita limpar o arquivo de entrada .Finalmente, você pode usar gexpand
no OSX, depois de instalar coreutils
com o Homebrew ( brew install coreutils
).
-i
para expand
para substituir apenas as guias principais em cada linha. Isso ajuda a evitar a substituição de guias que podem fazer parte do código.
input
é o mesmo arquivo que output
o bash clobbers o conteúdo antes mesmo de começar expand
. É assim que >
funciona.
Coletando os melhores comentários de resposta de Gene , a melhor solução, de longe, é usando 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"' {} \;
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 *.java
e *.JAVA
gostos)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çossponge
absorva 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 tmp
arquivo intermediário é usado.
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
sed -i '' $'s/\t/ /g' $(find . -name "*.txt")
Você pode usar o pr
comando 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=num
expande guias para num
espaçosPara 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:
expand
dado que ambos são POSIX? Por exemplo, tem uma opção de alteração em linha? Git
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 find
ou 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:
expand
isso 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\n
por \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)
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 -v
o sh
comando 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
ARG_MAX
comprimento. Isso é 128k em sistemas Linux, mas eu encontrei esse limite o tempo suficiente para não confiar no globbing do shell.
find
pode ser informado -maxdepth 1
e processa apenas as entradas do diretório que está sendo modificado, não a árvore inteira.
Eu costumava astyle
reentrar 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.
Pode-se usar vim
para 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.
:retab
alterará todas as guias de um arquivo, não aquelas no início. também depende de quais são suas configurações :tabstop
e :expandtab
no vimrc ou modeline, portanto, isso pode não funcionar.
tabstop
e expandtab
, funcionará se você estiver usando vim
. A menos que você tenha linhas de modo nos arquivos.
Minha recomendação é usar:
find . -name '*.lua' -exec ex '+%s/\t/ /g' -cwq {} \;
Comentários:
sed
é um editor de stream. Use ex
para 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 .find|xargs
vez 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 .ex
pode não estar disponível em todos os sistemas Unix. Substituí-lo por vi -e
pode 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/ /g
nã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.
/\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
xargs
dessa maneira é inútil, ineficiente e quebrado (pense em nomes de arquivos que contenham espaços ou aspas). Por que você não usa find
o -exec
comutador?
-print0
opções para encontrar / xargs. Gosto do xargs -exec
desde: a) Separação de preocupações b) pode ser trocado com o GNU paralelo mais facilmente.
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 {}' \;
Você pode usar find
com o tabs-to-spaces
pacote 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 tab
caractere por 2 spaces
em cada arquivo.
Nenhum corpo mencionado rpl
? Usando o rpl, você pode substituir qualquer string. Para converter guias em espaços,
rpl -R -e "\t" " " .
muito simples.
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
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;
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 ''
:
.git
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: /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.
Convertendo guias em espaço apenas em arquivos ".lua" [guias -> 2 espaços]
find . -iname "*.lua" -exec sed -i "s#\t# #g" '{}' \;
expand -t 4 input >output
)
expand -t 4
expandirá a guia em a\tb
3 espaços e a guia em aa\tb
2 espaços, exatamente como deveria ser. expand
leva em consideração o contexto de uma guia, sed
não substitui e substituirá a guia pela quantidade de espaços que você especificar, independentemente do contexto.
Use o vim-way:
$ ex +'bufdo retab' -cxa **/*.*
globstar
( **
) para recursão, ative por shopt -s globstar
.**/*.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 ex
editor + expand
utilitá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 $*
}
:retab
pode não funcionar , shell globbing é uma solução ruim para esse tipo de coisa , seu :s
comando substituirá qualquer quantidade de guias por 2 espaços (que você quase nunca quero), começando ex só para executar um :!expand
processo é bobagem ...