Как я могу преобразовать вкладки в пробелы в каждом файле каталога?


251

Как я могу преобразовать вкладки в пробелы в каждом файле каталога (возможно, рекурсивно)?

Кроме того, есть ли способ установить количество пробелов на вкладке?


Вы хотите заменить вкладки в файлах или именах файлов?
cppcoder

3
prэто замечательная утилита для этого. Смотрите этот ответ .
codeforester

Ответы:


69

Предупреждение: это сломает ваш репо.

Это приведет к повреждению бинарных файлов , включая те svn, которые находятся под .git,! Прочитайте комментарии перед использованием!

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

Исходный файл сохраняется как [filename].orig.

Замените '* .java' на конец файла того типа, который вы ищете. Таким образом, вы можете предотвратить случайное повреждение двоичных файлов.

Недостатки:

  • Заменим вкладки везде в файле.
  • Это займет много времени, если в этом каталоге окажется дамп SQL объемом 5 ГБ.

12
для визуального пространства, представляющего собой смесь вкладок и пробелов, этот подход дает неверное расширение.
пицца

7
Я также добавил бы средство сопоставления файлов, например, для поиска только файлов .php ./ -iname "* .php" -type f -exec sed -i 's / \ t / / g' {} \;
Даниэль Лука CleanUnicorn

98
НЕ ИСПОЛЬЗУЙТЕ САС! Если в строке есть встроенная вкладка, вы можете изменить код. Вот что должна была обрабатывать команда расширения . Использование expand.
Дэвид У.

5
@DavidW. Я просто обновил бы эту команду, чтобы заменить только вкладки в начале строки. find ./ -type f -exec sed -i 's/^\t/####/g' {} \;, Но я не знал о команде расширения - очень полезно!
Мартин Конечни

29
НЕ ИСПОЛЬЗУЙ! Этот ответ также разрушил мой локальный репозиторий git. Если у вас есть файлы, содержащие смешанные табуляции и пробелы, он вставит последовательность символов #. Вместо этого используйте ответ Джина или комментарий Дожа ниже.
кукла

344

Простая замена sed- это нормально, но не самое лучшее решение. Если между вкладками есть «лишние» пробелы, они все равно останутся после замены, поэтому поля будут неровными. Вкладки, развернутые в середине строк, также не будут работать правильно. В bash, мы можем сказать вместо

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

применить expandк каждому файлу Java в текущем дереве каталогов. Удалите / замените -nameаргумент, если вы ориентируетесь на некоторые другие типы файлов. Как упоминается в одном из комментариев, будьте очень осторожны при удалении -nameили использовании слабого подстановочного знака. Вы можете легко забить хранилище и другие скрытые файлы без намерения. Вот почему первоначальный ответ включал это:

Вы должны всегда делать резервную копию дерева, прежде чем пытаться что-то подобное на случай, если что-то пойдет не так.


2
@JeffreyMartinez Отличный вопрос. gniourf_gniourf отредактировал мой первоначальный ответ 11 ноября и сделал пренебрежительные замечания о том, что он не знает, как правильно пользоваться {}. Похоже, он не знал о том, $0когда -cиспользуется. Затем dimo414 изменился с моего использования temp в каталоге преобразования на /tmp, что будет намного медленнее, если он /tmpнаходится в другой точке монтирования. К сожалению, у меня нет Linux-бокса для тестирования вашего $0предложения. Но я думаю, что вы правы.
Джин

1
@Gene, спасибо за разъяснения, это звучит как stackoverflow хорошо: с. Хотя, пока я в этом, добавлю, что для правильного экранирования * .java пришлось использовать кавычки вокруг * .java.
Джеффри Мартинес

2
Если у кого-то возникла ошибка «неизвестный первичный или оператор» из find, то вот полная команда, которая исправит это:find . -name '*.java' ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;
Doge

4
Я думал, что в этом ответе недостаточно комментариев, как и было, так что это мое: если использовать use spongeиз joeyh.name/code/moreutils , вы можете написатьfind . -name '*.py' ! -type d -exec bash -c 'expand -t 8 "$0" | sponge "$0"' {} \;
tokland

8
Не будь глупым и используй find . -name '*', я только что уничтожил мой локальный репозиторий
Gautam

193

Попробуйте инструмент командной строки expand.

expand -i -t 4 input | sponge output

где

  • -i используется для расширения только ведущих вкладок в каждой строке;
  • -t 4 означает, что каждая вкладка будет преобразована в 4 символа пробела (по умолчанию 8).
  • spongeиз moreutilsпакета, и избегает очистки входного файла .

Наконец, вы можете использовать gexpandна OSX, после установки coreutilsс Homebrew ( brew install coreutils).


5
Это одна из GNU_Core_Utilities
kev

32
Вы должны перейти -iна expandзамену только ведущих вкладок в каждой строке. Это помогает избежать замены вкладок, которые могут быть частью кода.
Вопросы Кволонела

10
как насчет каждого файла в каталоге рекурсивно?
Анбизкад

4
Каждый раз, когда я пытаюсь использовать это, он очищает некоторые (обычно все) файлы. : \
ThorSummoner

5
@ThorSummoner: if input- это тот же файл, что outputи bash, который забивает содержимое еще до запуска expand. Вот как это >работает.
Роберт Симер

34

Собирая лучшие комментарии от ответа Гена , лучшее решение на сегодняшний день, является использование spongeиз moreutils .

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

Объяснение:

  • ./ рекурсивно ищет в текущем каталоге
  • -inameсовпадение без учета регистра (как для лайков, так *.javaи для *.JAVAлайков)
  • type -f находит только обычные файлы (без каталогов, двоичных файлов или символических ссылок)
  • -exec bash -c выполнить следующие команды в подоболочке для каждого имени файла, {}
  • expand -t 4 расширяет все вкладки до 4 мест
  • spongeвпитать стандартный ввод (из expand) и записать в файл (тот же) *.

ПРИМЕЧАНИЕ : * Простое перенаправление файлов ( > "$0") здесь не будет работать, потому что оно слишком быстро перезапишет файл .

Преимущество : все исходные права доступа к файлам сохраняются и промежуточные tmpфайлы не используются.


2
TIL: изумительная команда губки, после 15 лет использования Linux. Спасибо загадочному рыцарю из интернета.
sscarduzio

19

Используйте обратную косую черту sed.

На Linux:

  • Замените все вкладки с 1 дефисом во всех файлах * .txt:

    sed -i $'s/\t/-/g' *.txt
  • Замените все вкладки на 1 пробел на месте, во всех файлах * .txt:

    sed -i $'s/\t/ /g' *.txt
  • Замените все вкладки с 4 пробелами во всех * .txt файлах:

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

На маке:

  • Замените все вкладки с 4 пробелами во всех * .txt файлах:

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

2
@ Машаsed -i '' $'s/\t/ /g' $(find . -name "*.txt")
xyzale

Этот ответ кажется самым простым.
Ян Кинг Инь

6

Вы можете использовать общедоступную prкоманду (страница руководства здесь ). Например, чтобы преобразовать вкладки в четыре пробела, сделайте это:

pr -t -e=4 file > file.expanded
  • -t подавляет заголовки
  • -e=numрасширяет вкладки до numпробелов

Чтобы рекурсивно преобразовать все файлы в дереве каталогов, пропуская двоичные файлы:

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

Логика пропуска двоичных файлов взята из этого поста .

НОТА:

  1. Это может быть опасно в git или svn repo
  2. Это неправильное решение, если у вас есть файлы кода, в которых вкладки встроены в строковые литералы

1
Есть ли преимущество перед тем, expandчто оба являются POSIX? Например, у него есть встроенная опция изменения? Git safety at: stackoverflow.com/a/52136507/895245
Чиро Сантилли 郝海东 '病 六四 事件 法轮功

5

Как я могу преобразовать вкладки в пробелы в каждом файле каталога (возможно, рекурсивно)?

Обычно это не то, что вы хотите.

Вы хотите сделать это для изображений PNG? PDF файлы? Каталог .git? Ваш Makefile(который требует вкладки)? SQL-дамп 5 Гб

Теоретически вы можете передать множество опций исключения findили что-то еще, что вы используете; но это хрупко, и сломается, как только вы добавите другие двоичные файлы.

То, что вы хотите, это как минимум:

  1. Пропустить файлы определенного размера.
  2. Определите, является ли файл двоичным, проверив наличие байта NULL.
  3. Заменяйте вкладки только в начале файла ( expandделает это, sed не делает).

Насколько я знаю, не существует "стандартной" утилиты Unix, которая могла бы сделать это, и это не очень легко сделать с однострочником оболочки, поэтому необходим скрипт.

Некоторое время назад я создал небольшой скрипт под названием sanitize_files, который делает именно это. Также исправлены некоторые другие распространенные вещи, такие как замена \r\nна \n, добавление трейлинга \nи т. Д.

Вы можете найти упрощенный скрипт без дополнительных функций и аргументов командной строки ниже, но я рекомендую использовать вышеописанный скрипт, так как он с большей вероятностью получит исправления ошибок и другие обновления, чем этот пост.

Я также хотел бы отметить, в ответ на некоторые другие ответы здесь, что использование глобализации оболочки не является надежным способом сделать это, потому что рано или поздно у вас будет больше файлов, чем уместится ARG_MAX(в современных В системах Linux это 128k, что может показаться большим, но рано или поздно этого не достаточно).


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

В git бинарная проверка очень проста: stackoverflow.com/a/52136507/895245
Сиро Сантилли (iro 18 病 C 事件 法轮功

5

Мне нравится пример "найти" выше для рекурсивного приложения. Чтобы адаптировать его к нерекурсивному, изменяя только файлы в текущем каталоге, которые совпадают с подстановочными знаками, расширения оболочки может быть достаточно для небольшого количества файлов:

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

Если вы хотите, чтобы он молчал после того, как вы уверены, что он работает, просто бросьте команду -von shв конце.

Конечно, вы можете выбрать любой набор файлов в первой команде. Например, перечислите только определенный подкаталог (или каталоги) контролируемым образом, например:

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

Или, в свою очередь, запустите find (1) с некоторой комбинацией параметров глубины и т.д .:

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

1
Глобализация оболочки рано или поздно прекратится, потому что общее количество имен файлов может быть только ARG_MAXдлины. Это 128 КБ в системах Linux, но я сталкивался с этим ограничением достаточно раз, чтобы не полагаться на глобализацию оболочки.
Мартин Турной

1
Вам не нужно адаптировать их. findможно сказать -maxdepth 1, и он обрабатывает только записи изменяемого каталога, а не все дерево.
ShadowRanger

4

Я использовал astyleдля повторного отступа весь мой код C / C ++ после нахождения смешанных символов табуляции и пробелов. Он также имеет опции для принудительного использования определенного стиля скобок, если хотите.


4

Для этого можно использовать vim:

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

Как заявил Carpetsmoker, он будет перезагружен в соответствии с вашими vimнастройками. И модели в файлах, если есть. Также он заменит вкладки не только в начале строк. Что не то, что вы обычно хотите. Например, у вас могут быть литералы, содержащие вкладки.


:retabизменит все вкладки в файле, а не те, что в начале. это также зависит от ваших настроек :tabstopи :expandtabнастроек в vimrc или modeline, так что это может вообще не работать.
Мартин Турной

@Carpetsmoker Хорошее замечание о вкладках в начале строк. Любое из решений здесь обрабатывает этот случай? Что же касается tabstopи expandtabнастроек, он будет работать, если вы используете vim. Если у вас нет строк режима в файлах.
x-yuri

@ х-юри хороший вопрос, но вообще спорный. Большинство людей используют не настоящие вкладки в литералах.
Рикардо Крус

4

Я рекомендую использовать:

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

Комментарии:

  1. Используйте в месте редактирования. Храните резервные копии в VCS. Нет необходимости создавать * .orig файлы. Хорошей практикой является сравнение результата с последним коммитом, чтобы убедиться, что в любом случае это сработало так, как ожидалось.
  2. sedпотоковый редактор Используйте exдля редактирования на месте. Это позволяет избежать создания дополнительных временных файлов и порождать оболочки для каждой замены, как показано в верхнем ответе .
  3. ВНИМАНИЕ: Это портит все вкладки, а не только те, которые используются для отступа. Также он не делает контекстную замену вкладок. Этого было достаточно для моего варианта использования. Но не может быть приемлемым для вас.
  4. РЕДАКТИРОВАТЬ: более ранняя версия этого ответа используется find|xargsвместо find -exec. Как указывает @ gniourf-gniourf, это приводит к проблемам с пробелами, кавычками и контрольными символами в именах файлов cf. Уилер .

exможет быть недоступно в каждой системе Unix. Замена его vi -eможет работать на большем количестве машин. Кроме того, ваше регулярное выражение заменяет любое количество начальных символов табуляции двумя пробелами. Замените регулярное выражение, +%s/\t/ /gчтобы не разрушать многоуровневый отступ. Однако это также влияет на символы табуляции, которые не используются для отступа.
Лукас Шмельцайзен

ex является частью POSIX [1], поэтому должен быть доступен. Хороший вопрос о многоуровневой индендации. Я на самом деле использовал /\t/ /вариант в моих файлах, но решил /\t\+//не ломать вкладки без отступов. Пропустил проблемы с несколькими отступами! Обновление ответа. [1] man7.org/linux/man-pages/man1/ex.1p.html#SEE%C2%A0ALSO
Генрих Хартманн

2
Использование xargsтаким способом бесполезно, неэффективно и неэффективно (представьте имена файлов, содержащие пробелы или кавычки). Почему бы вам не использовать find«s -execпереключатель вместо этого?
gniourf_gniourf

Я бы сказал, что имена файлов с пробелами и кавычками не работают; ) Если вам нужна поддержка, я бы выбрал: -print0опции для поиска / xargs. Мне больше нравится xargs, -execтак как: а) разделение проблем; б) его можно поменять местами с помощью параллельной GNU.
Генрих Хартманн

Обновлено добавление комментариев @gniourf_gniourf.
Генрих Хартманн

4

Чтобы рекурсивно преобразовать все файлы Java в каталоге, чтобы использовать 4 пробела вместо вкладки:

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

Чем этот ответ отличается от того, который был опубликован 4 года назад?
PP

2
Так же как и ваш ответ. На самом деле, это плохая версия ответа Джина: 1) Ответ Джина позаботится о каталогах с тем же именем. 2) Он не двигается, если расширение не удалось.
PP

4

Вы можете использовать findс tabs-to-spacesпакетом для этого.

Сначала установите tabs-to-spaces

npm install -g tabs-to-spaces

затем запустите эту команду из корневого каталога вашего проекта;

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

Это заменит каждый tabсимвол с 2 spacesв каждом файле.


3

Тело не упомянуто rpl? Используя rpl вы можете заменить любую строку. Чтобы преобразовать вкладки в пробелы,

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

очень просто.


1
Это повредило все бинарные файлы в моем репо.
Аарон Франке

1
Отличная команда, но потенциально опасная для рекурсивных и всех файлов в папке, как указано выше. Я бы добавил параметр --dry-run «на всякий случай», чтобы убедиться, что вы находитесь в правильной папке.
MortimerCat

2

Использование, expandкак предлагается в других ответах, кажется наиболее логичным подходом для одной только этой задачи.

Тем не менее, это также может быть сделано с Bash и Awk на случай, если вы захотите сделать некоторые другие модификации вместе с ним.

Если вы используете Bash 4.0 или выше, встроенный модуль shopt globstar может использоваться для рекурсивного поиска **.

С помощью GNU Awk версии 4.1 или выше можно внести изменения, подобные файлам «inplace»:

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

Если вы хотите установить количество пробелов на вкладке:

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

2

Загрузите и запустите следующий сценарий для рекурсивного преобразования жестких вкладок в программные вкладки в текстовых файлах.

Выполните сценарий из папки, которая содержит текстовые файлы.

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

2

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"
)

Действовать на все файлы в текущем каталоге:

git-tab-to-space

Действовать только для файлов C или C ++:

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

Вы, вероятно, хотите этого особенно из-за тех раздражающих Makefiles, которые требуют вкладок.

Команда git grep --cached -Il '':

  • перечисляет только отслеживаемые файлы, так что ничего внутри .git
  • исключает каталоги, двоичные файлы (будут повреждены) и символические ссылки (будут преобразованы в обычные файлы)

как объяснено на: Как перечислить все текстовые (недвоичные) файлы в репозитории git?

chmod --referenceоставляет права доступа к файлу без изменений: /unix/20645/clone-ownership-and-permissions-from-another-file К сожалению, я не могу найти краткую альтернативу POSIX .

Если ваша кодовая база имела безумную идею разрешить функциональные необработанные вкладки в строках, используйте:

expand -i

и затем получайте удовольствие, просматривая все незапуски линейных вкладок одну за другой, с помощью которых вы можете перечислить: Возможно ли использовать git grep для вкладок?

Проверено на Ubuntu 18.04.


-1

Преобразование вкладок в пробелы только в файлах ".lua" [вкладки -> 2 пробела]

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

Очевидно, что объем пространства, на которое расширяется вкладка, зависит от контекста. Таким образом, sed - совершенно неподходящий инструмент для этой задачи.
Свен

?? @ Свен, моя команда sed делает то же самое, что и команда расширения ( expand -t 4 input >output)
Маках

3
Конечно нет. expand -t 4развернет вкладку a\tbдо 3 пробелов и вкладку aa\tbдо 2 пробелов, как и должно быть. expandучитывает контекст вкладки, sedне изменяет и заменяет вкладку количеством пробелов, указанным вами, независимо от контекста.
Свен

-1

Используйте vim-way:

$ ex +'bufdo retab' -cxa **/*.*
  • Сделайте резервную копию! перед выполнением вышеуказанной команды, так как она может повредить ваши двоичные файлы.
  • Чтобы использовать globstar( **) для рекурсии, активируйте с помощью shopt -s globstar.
  • Чтобы указать конкретный тип файла, используйте, например: **/*.c.

Чтобы изменить вкладку, добавьте +'set ts=2'.

Однако недостатком является то, что он может заменить вкладки внутри строк .

Так что для немного лучшего решения (используя замену), попробуйте:

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

Или используя exредактор + expandутилиту:

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

О конечных пробелах см .: Как удалить конечные пробелы для нескольких файлов?


Вы можете добавить следующую функцию в свой .bash_profile:

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

Я отклонил многие ответы в этой теме, не только ваши ;-) Причины таковы: :retabможет не работать вообще , глобализация оболочки - плохое решение для такого рода вещей , ваша :sкоманда заменит любое количество вкладок двумя пробелами (которые вы почти никогда не хочу), начинать бывшего просто чтобы запустить :!expandпроцесс глупо ...
Мартин Турной

... и все ваши решения будут блокировать двоичные файлы и тому подобное (например, файлы .png, файлы .pdf и т. д.)
Мартин Турной

Откровенно говоря, это ужасное предложение для документации - чтобы понять это, нужно быть близко знакомым с рядом довольно непрозрачных синтаксических и семантических проблем нескольких программ.
Иосип Роден
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.