如何将制表符转换为目录的每个文件中的空格?


251

如何将制表符转换为目录的每个文件中的空格(可能是递归的)?

另外,是否可以设置每个标签的空格数?


您要替换文件或文件名中的选项卡吗?
cppcoder 2012年

3
pr是一个很棒的工具。看到这个答案
codeforester

Answers:


69

警告:这将破坏您的回购。

将损坏的二进制文件,包括那些在svn.git!使用前请阅读评论!

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

原始文件另存为[filename].orig

将“ * .java”替换为您要查找的文件类型的文件结尾。这样,您可以防止二进制文件的意外损坏。

缺点:

  • 将替换文件中所有位置的标签。
  • 如果您恰好在此目录中有5GB的SQL转储,将花费很长时间。

12
对于包含制表符和空格的可视空间,此方法会导致错误的扩展。
披萨

7
我还将添加一个文件匹配器,例如仅查找.php文件。/ -iname“ * .php” -type f -exec sed -i's / \ t / / g'{} \;
Daniel Luca CleanUnicorn

98
请勿使用SED!如果字符串中有嵌入的选项卡,则可能会破坏代码。这就是expand命令要处理的内容。使用expand
David W.

5
@DavidW。我只需更新此命令即可仅替换行首的制表符。find ./ -type f -exec sed -i 's/^\t/####/g' {} \;。但是我不知道expand命令-非常有用!
Martin Konecny 2014年

29
不使用!这个答案也破坏了我本地的git仓库。如果文件包含制表符和空格混合,它将插入#的序列。请改用Gene的答案或Doge的评论。
2014年

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月11日编辑了我的原始答案,并贬低了关于不知道正确使用方法的言论{}。看起来他不知道$0何时-c使用。然后dimo414从我在转换目录中使用temp更改为/tmp,如果/tmp在不同的挂载点上,它将慢得多。不幸的是,我没有Linux盒子可以测试您的$0建议。但是我认为你是对的。
2013年

1
@Gene,感谢您的澄清,听起来像stackoverflow好:p。虽然我正在讨论,但我将添加“ * .java”周围的引号,以正确地转义* .java。
杰弗里·马丁内斯

2
如果有人在查找中遇到“未知的主要操作员或操作员”错误,那么这里是将修复此问题的完整命令:find . -name '*.java' ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;
Doge 2014年

4
我想这个答案已经没有足够的意见,因为它是,所以这是我:如果使用使用spongejoeyh.name/code/moreutils,你可以写find . -name '*.py' ! -type d -exec bash -c 'expand -t 8 "$0" | sponge "$0"' {} \;
tokland

8
不要愚蠢和使用find . -name '*',我只是销毁了我本地的git repo
Gautam

193

试试命令行工具expand

expand -i -t 4 input | sponge output

哪里

  • -i 用于仅展开每行上的前导制表符;
  • -t 4 表示每个标签都将转换为4个空白字符(默认为8个)。
  • sponge来自moreutils软件包,并且避免清除输入文件

最后,gexpandcoreutils通过Homebrew(brew install coreutils)安装后,可以在OSX上使用。



32
您应该-i转到expand以仅替换每行上的前导标签。这有助于避免替换可能是代码一部分的选项卡。
Quolonel问题

10
如何递归目录中的每个文件?
ahnbizcad

4
每次尝试使用此功能时,它都会清空部分(通常是全部)文件。:\
ThorSummoner 2015年

5
@ThorSummoner:如果与bash input相同,则文件output甚至在启动前就破坏了内容expand。这是如何>工作的。
罗伯特·西默

34

从收集的最好注解基因的答案,目前最好的解决办法,是通过使用spongemoreutils

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

说明:

  • ./ 从当前目录递归搜索
  • -iname是不区分大小写的匹配项(对于“ likes” *.java和“ *.JAVAlikes”)
  • type -f 仅查找常规文件(无目录,二进制文件或符号链接)
  • -exec bash -c 在子外壳中为每个文件名执行以下命令, {}
  • expand -t 4 将所有TAB扩展到4个空格
  • sponge吸收标准输入(来自expand)并写入文件(相同)*。

注意:*一个简单的文件重定向(> "$0")在这里不起作用,因为它会很快覆盖文件

优点:保留所有原始文件权限,并且不tmp使用任何中间文件。


2
TIL:在使用Linux 15年后的神奇海绵命令。谢谢来自互联网的神秘骑士。
sscarduzio

19

使用反斜杠转义的sed

在Linux上:

  • 在所有* .txt文件中,将所有标签替换为1个连字符:

    sed -i $'s/\t/-/g' *.txt
  • 在所有* .txt文件中,将所有标签替换为1个空格:

    sed -i $'s/\t/ /g' *.txt
  • 在所有* .txt文件中,将所有标签替换为4个空格:

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

在Mac上:

  • 在所有* .txt文件中,将所有标签替换为4个空格:

    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安全性:stackoverflow.com/a/52136507/895245
Ciro Santilli郝海东冠状病六四事件法轮功

5

如何将制表符转换为目录的每个文件中的空格(可能是递归的)?

这通常不是您想要的。

您要对png图像执行此操作吗?PDF文件?.git目录?您的 Makefile需要标签)?5GB的SQL转储?

从理论上讲,您可以将大量排除选项传递给find或您使用的任何其他选项;但这很脆弱,一旦添加其他二进制文件,它就会崩溃。

您想要的至少是:

  1. 跳过特定大小的文件。
  2. 通过检查是否存在NULL字节来检测文件是否为二进制。
  3. 仅替换文件开头的选项卡(expand这样做,sed 不是)。

据我所知,没有“标准的” Unix实用程序可以做到这一点,而且使用shell一线式处理不是很容易,因此需要一个脚本。

不久前,我创建了一个名为sanitize_files的小脚本 ,它确实可以做到这一点。它还修复了一些其他常见的问题,例如替换\r\n\n,添加结尾\n等。

您可以在下面找到一个没有附加功能和命令行参数的简化脚本,但我建议您使用上面的脚本,因为与本文相比,它更可能收到错误修复和其他更新。

我还想指出,针对此处的其他一些答案,使用shell globbing 并不是一种健壮的方法,因为迟早您最终会得到比适合的文件更多的文件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)


5

我喜欢上面针对递归应用程序的“查找”示例。为了使其适应非递归操作,仅更改当前目录中与通配符匹配的文件,shell glob扩展足以容纳少量文件:

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

如果您希望它在您相信它可以正常工作后保持沉默,则只需-vsh命令末尾放置。

当然,您可以在第一个命令中选择任何文件集。例如,以受控方式仅列出特定的子目录(或多个目录),如下所示:

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
Shell globbing迟早会中断,因为文件名的总数只能是一定ARG_MAX长度。在Linux系统上,这是128k,但是我已经遇到了这个限制足够的时间,以至于不依赖于外壳程序。
马丁·图尔诺伊

1
您实际上不需要调整它们。find可以知道-maxdepth 1,它仅处理要修改的目录的条目,而不处理整个树。
ShadowRanger 2015年

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和模式行,所以这可能不是在所有的工作。
马丁·图尔诺伊

@Carpetsmoker关于行开头的选项卡的要点。这里有没有解决方案可以处理这种情况?至于tabstopexpandtab设置,如果您使用,它将可以解决vim。除非文件中有模式行。
x-yuri

@ x-yuri很好的问题,但通常没有意义。大多数人在文字中使用\ t不是实际的制表符。
里卡多·克鲁兹

4

我的建议是使用:

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

注释:

  1. 用于就地编辑。将备份保存在VCS中。无需产生* .orig文件。最好将结果与您的上一次提交进行比较,以确保在任何情况下都能按预期工作。
  2. sed是流编辑器。使用ex就地编辑了。这样可以避免为每次替换创建额外的临时文件和生成shell,就像在顶部回答中那样
  3. 警告:这将与所有选项卡弄乱,不仅是用于缩进的选项卡。同样,它也不执行上下文相关的选项卡替换。这对于我的用例来说已经足够了。但对于您来说可能不可接受。
  4. 编辑:此答案的早期版本find|xargs代替find -exec。正如@ gniourf-gniourf指出的那样,这会导致文件名cf中的空格,引号和控制字符出现问题。惠勒

ex可能并非在每个Unix系统上都可用。将其替换为vi -e可能在更多计算机上工作。同样,您的正则表达式用两个空格替换任意数量的起始制表符。将regex替换+%s/\t/ /g为不会破坏多级缩进。但是,这也会影响未用于缩进的制表符。
卢卡斯·舒梅利森

ex是POSIX [1]的一部分,因此应该可用。关于多层次设计的好处。我实际上/\t/ /在文件上使用了变体,但是选择了/\t\+//不破坏非缩进选项卡。错过了多缩进的问题!更新答案。[1] man7.org/linux/man-pages/man1/ex.1p.html#SEE%C2%A0ALSO
Heinrich Hartmann

2
xargs以这种方式使用是无用的,效率低下的和损坏的(想想包含空格或引号的文件名)。为什么不使用find' -exec开关呢?
gniourf_gniourf

我认为带空格和引号的文件名已损坏;)如果您需要支持,我会选择:-print0查找/ xargs的选项。我之所以喜欢xargs,是-exec因为:a)关注点分离b)它可以更轻松地与GNU parallel交换。
海因里希·哈特曼

更新添加了@gniourf_gniourf注释。
Heinrich Hartmann

4

要以递归方式将目录中的所有Java文件转换为使用4个空格而不是制表符:

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

如何从这个答案不同其中在4年前发布?
PP

2
您的答案也是如此。实际上,这是Gene答案的次等版本:1)Gene的答案负责同名目录。2)如果扩展失败,它不会移动
PP

4

您可以使用findtabs-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或更高版本,可以对sed进行“就地”文件修改:

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)$'

您可能特别希望这样做,因为那些烦人的Makefile需要使用制表符。

命令git grep --cached -Il ''

  • 仅列出跟踪的文件,因此里面没有任何内容 .git
  • 排除目录,二进制文件(将被破坏)和符号链接(将被转换为常规文件)

如以下内容所述:如何列出git存储库中的所有文本(非二进制)文件?

chmod --reference保持文件权限不变:https : //unix.stackexchange.com/questions/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是完全不适合该任务的工具。
2015年

?? @Sven,我的sed命令执行的功能与expand命令(expand -t 4 input >output)的功能相同
Makah 2015年

3
当然不是。expand -t 4会将标签页扩展a\tb为3个空格,并将标签页扩展aa\tb为2个空格。expand考虑制表符的上下文,sed不考虑制表符,并且无论上下文如何,都将使用您指定的空格量替换制表符。
2015年

-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可能根本不起作用shell globbing对于这种情况是一个不好的解决方案,您的:s命令将用2个空格替换任意数量的制表符(您几乎永不想要),仅从运行:!expand过程开始
就很

...以及您所有的解决方案都会破坏二进制文件等(例如.png文件,.pdf文件等)
Martin Tournoij 2015年

坦率地说,这是对文档的一个可怕建议-必须熟悉一些程序的一些相当不透明的语法和语义问题才能理解这一点。
Josip Rodin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.