如何将选项卡转换为目录的每个文件中的空格?

cnd*_*cnd 232 unix bash shell spaces in-place

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

另外,有没有办法设置每个标签的空格数?

Gen*_*ene 328

简单的替换sed是可以的,但不是最好的解决方案.如果标签之间存在"额外"空格,则替换后它们仍然存在,因此边距将是不规则的.在行中间展开的选项卡也无法正常工作.在bash,我们可以说

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

应用于expand当前目录树中的每个Java文件.-name如果您要定位其他一些文件类型,请删除/替换参数.正如其中一条评论所提到的,在删除-name或使用弱通配符时要非常小心.您可以轻松地破坏存储库和其他隐藏文件.这就是为什么最初的答案包括:

在尝试这样的事情之前,您应该始终制作树的备份副本,以防出现问题.

  • 不要愚蠢并使用`find.-name'*'`,我刚刚销毁了我当地的git repo (7认同)
  • 我认为这个答案没有足够的评论,所以这是我的:如果使用来自https://joeyh.name/code/moreutils/的`sponge`,你可以写`find.-name'*.py'!-type d -exec bash -c'expansion -t 8"$ 0"| 海绵"$ 0"'{} \;` (4认同)
  • @JeffreyMartinez很好的问题。gniourf_gniourf在11月11日编辑了我的原始答案,并贬低了关于不知道正确使用`{}的方式的言论。看起来在使用`-c`时他不了解`$ 0`。然后dimo414从我在转换目录中使用temp更改为`/ tmp`,如果`/ tmp`位于不同的挂载点,则速度会慢得多。不幸的是,我没有可用的Linux机器来测试您的$ 0建议。但是我认为你是对的。 (2认同)
  • 如果有人从find中发现'未知的主要或操作符'错误,那么这里是完整的命令来修复它:`find.-name'*.java'!-type d -exec bash -c'expansion -t 4"$ 0">/tmp/e && mv/tmp/e"$ 0"'{} \;` (2认同)

kev*_*kev 186

尝试使用命令行工具expand.

expand -i -t 4 input | sponge output
Run Code Online (Sandbox Code Playgroud)

哪里

最后,您可以gexpandcoreutils使用Homebrew(brew install coreutils)安装后在OSX上使用.

  • 您应该将`-i`传递给`expand`以仅替换每行上的前导标签.这有助于避免替换可能是代码一部分的选项卡. (30认同)
  • 递归地对目录中的每个文件怎么样? (10认同)
  • 它是[GNU_Core_Utilities]之一(http://en.wikipedia.org/wiki/GNU_Core_Utilities) (5认同)
  • @ThorSummoner:如果`input`和`output`是同一个文件,bash会在开始`expand`之前破坏内容.这就是`>`的工作原理. (5认同)
  • 每次我尝试使用它都会使一些(通常是所有)文件空白.:\ (4认同)
  • 注意:您正在创建新文件,新文件可能具有与您开始使用的文件不同的权限.我有一些权限为"0600"的文件,使用`expand`后,新文件的默认权限为`0664`.使用`sponge`并创建一个新文件具有相同的效果.使用`sponge`而不是创建新文件保留了原始权限.示例:`expand --tabs = 4输入| 海绵输入`.请注意在`sponge`示例中使用`|`而不是`>`. (4认同)
  • 这可以放在for循环中吗?当我尝试我得到空输出文件 (3认同)
  • @ThorSummoner您应该查看`sponge`,这对于获取stdout并将其重定向回原始文件非常有用.它的工作原理是保存所有输出到stdin的输出,等待管道完成,然后才打开并写入原始文件.它是`moreutils`包的一部分(默认情况下通常不安装). (3认同)
  • 对于那些不使用GNU Core Utilities的系统,由于它是由The Open Group的Single Unix Specification标准化的,因此你有很大的机会安装`expand`.参见问题6,该文章来自2001年,虽然已经应用了一些更新,因此发布的年份是2004年:[`expand`](http://pubs.opengroup.org/onlinepubs/009695399/utilities/expand.html) (2认同)
  • expand -t 4 Foo | 海绵Foo是我需要的调用 (2认同)

Mar*_*ett 66

警告:这会破坏你的回购.

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

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

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

缺点:

  • 将替换文件中的标签.
  • 如果您碰巧在此目录中有5GB的SQL转储,则需要很长时间.

  • 不要使用SED!如果字符串中有嵌入式选项卡,您最终可能会破坏代码.这就是[expand](http://man.cx/expand)命令要处理的内容.使用`expand`. (90认同)
  • 不使用!这个答案也破坏了我的本地git存储库.如果你有包含混合标签和空格的文件,它将插入#的序列.请使用Gene的答案或Doge的评论. (27认同)
  • 对于混合了制表符和空格的可视空间,这种方法会产生不正确的扩展. (11认同)
  • 我还要添加一个文件匹配器,例如只有.php文件才能找到./ -iname"*.php"-type f -exec sed -i's /\t// g'{} \; (6认同)
  • @DavidW.我只是更新此命令以仅替换行开头的制表符.```find ./ -type f -exec sed -i's/^\t/####/g'{} \;```.但我不知道扩展命令 - 非常有用! (5认同)
  • 答案的命令刚刚破坏了我的本地git存储库.因人而异. (4认同)
  • 我不知道为什么这会杀死你的本地存储库,它不会对我这样做.`#`字符可能需要被实际空格替换,我认为这就是答案中的"#are spaces".但是`^`没有帮助:你最终只更换第一个标签,后续标签不会被替换,即无用! (2认同)
  • 显然,选项卡扩展到的空间量取决于上下文.因此,`sed`是完成该任务的完全不合适的工具. (2认同)

not*_*bit 26

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

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

说明:

  • ./ 从当前目录递归搜索
  • -iname是一个不区分大小写的匹配(对于两者*.java*.JAVA喜欢)
  • type -f 只查找常规文件(没有目录,二进制文件或符号链接)
  • -exec bash -c 在子shell中为每个文件名执行以下命令, {}
  • expand -t 4 将所有TAB扩展为4个空格
  • sponge吸收标准输入(从expand)并写入文件(同一个)*.

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

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

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

e9t*_*e9t 16

使用反斜杠转义sed.

在linux上:

在mac上:

  • @Маша`sed -i''$'s / \ t / / g'$(查找。-name“ * .txt”)` (2认同)

cod*_*ter 8

您可以使用一般可用的pr命令(此处的手册页)。例如,要将制表符转换为四个空格,请执行以下操作:

pr -t -e=4 file > file.expanded
Run Code Online (Sandbox Code Playgroud)
  • -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
Run Code Online (Sandbox Code Playgroud)

跳过二进制文件的逻辑来自这篇文章

笔记:

  1. 在 git 或 svn repo 中这样做可能很危险
  2. 如果您的代码文件在字符串文字中嵌入了制表符,这不是正确的解决方案


drc*_*uck 5

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

ls *.java | awk '{print "expand -t 4 ", $0, " > /tmp/e; mv /tmp/e ", $0}' | sh -v
Run Code Online (Sandbox Code Playgroud)

如果你想它沉默之后,你相信它的作品,只是降-vsh末命令。

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

ls mod/*/*.php | awk '{print "expand -t 4 ", $0, " > /tmp/e; mv /tmp/e ", $0}' | sh
Run Code Online (Sandbox Code Playgroud)

或者反过来运行 find(1) 并结合一些深度参数等:

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


Mar*_*oij 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)
Run Code Online (Sandbox Code Playgroud)


Hei*_*ann 5

我的建议是使用:

find . -name '*.lua' -exec ex '+%s/\t/  /g' -cwq {} \;
Run Code Online (Sandbox Code Playgroud)

注释:

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

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

Har*_*ria 5

为此,您可以使用findwithtabs-to-spaces包。

首先,安装 tabs-to-spaces

npm install -g tabs-to-spaces
Run Code Online (Sandbox Code Playgroud)

然后,从项目的根目录运行此命令;

find . -name '*' -exec t2s --spaces 2 {} \;
Run Code Online (Sandbox Code Playgroud)

这将在每个文件tab中用 2替换每个字符spaces