【问题标题】:Commandline find, sed, exec命令行查找、sed、执行
【发布时间】:2011-08-01 01:41:52
【问题描述】:

我在一个文件夹、子文件夹中有一堆文件,我正在尝试制作某种单行文件,以便偶尔快速复制/粘贴。

内容为(太长无法粘贴):http://pastebin.com/4aZCPbwT

我尝试了以下命令:

列出所有文件及其目录

find . -name '[!.]*'

将“命名空间”的所有实例替换为“测试:

find . -name '[!.]*' -print0 | sed 's/Namespace/Test/gI' | xargs -i -0 echo '{}'

我需要做的是:

替换上面的文件夹名称,并将文件夹(包括文件)复制到另一个位置。如果文件夹不存在(它们很可能不会),请创建文件夹 - 但是,我不需要其中一些文件夹,例如 ./app,因为此文件夹存在。我可以为此使用 -wholename './app'。

当它们被复制时,我需要替换每个文件中的一些文本,与上面相同(带有测试的命名空间 - 当然也出现在文件中并保存它们)。

我会想象这样的事情:

-print -exec sed -i 's/Namespace/Test/gI' {} \;

这三件事可以一次性完成吗?替换文件中的文本(命名空间 测试),用 cp -p 复制文件,包括它们的目录(不想覆盖文件夹),但用上面的方法重命名每个目录/文件(命名空间 测试)。

非常感谢:-)

【问题讨论】:

  • 为什么要浪费时间想出一个班轮......我只会写一个简单的带有for循环的bash脚本,甚至更好的是python脚本。这也可以在 python 交互式 shell 中很好地完成。
  • 嗯,好点。似乎将某些内容复制/粘贴到我的终端会更容易:-)。我的 Bash 技能不是那么强,所以我可能无法编写完整的脚本。我只能做小的随机命令。
  • 你发布的命令表明你有一些技巧:)
  • 为什么谢谢你......你说得真好:-)。我想我可以再谷歌一下,还不如自己学着做。

标签: bash scripting find exec


【解决方案1】:

我没有测试过这个,但我认为这就是你所追求的。

find . -name '[!.]*' -print | while read line; do nfile=`echo "$line" | sed 's/Namespace/Test/gI'`; mkdir -p "`dirname $nfile`"; cp -p "$line" "$nfile"; sed -i 's/Namespace/Test/gI' "$nfile"; done

【讨论】:

  • 这应该对我有帮助......非常感谢。我将它分解一下,然后一块一块地做。
【解决方案2】:

除了在下面详细描述如何之外,这种方法也可能是独一无二的,因为它结合了内置调试。它基本上不做任何写的事情,除了编译并将所有它认为应该执行的命令保存到一个变量中以执行请求的工作。

它还尽可能明确地避免循环。除了sed 递归搜索多个pattern 匹配项,据我所知,没有其他递归。

最后,这完全是 null 分隔的 - 除了 null 之外,它不会触发任何文件名中的任何字符。我认为你不应该这样。

顺便说一句,这真的快。看:

% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED <<SED
> :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*\(.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo <<EOF
> Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh <<EORUN
> $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" |  tail -n 2 )

   <actual process time used:>
    0.06s user 0.03s system 106% cpu 0.090 total

   <output from wc:>

    Lines  Words  Bytes
    115     362   20691 -

    <output from tail:>

    mv .config/replacement_word-chrome-beta/Default/.../googlestars \
    .config/replacement_word-chrome-beta/Default/.../replacement_wordstars        

注意:上述function 可能需要GNU 版本的sed 和find 才能正确处理find printf 和sed -z -e 和:;recursive regex test;t 调用。如果您无法使用这些功能,则可能会通过一些小的调整来复制功能。

这应该可以从头到尾完成您想做的所有事情,而且很少大惊小怪。我用sed 做了fork,但我也在练习一些sed 递归分支技术,所以这就是我在这里的原因。我猜这有点像在理发学校打折理发。这是工作流程:

  • rm -rf ${UNNECESSARY}
    • 我故意遗漏了任何可能删除或破坏任何类型数据的函数调用。您提到 ./app 可能不需要。事先将其删除或移动到其他地方,或者,您可以在\( -path PATTERN -exec rm -rf \{\} \) 例程中构建find 以编程方式执行此操作,但那是您的全部。
  • _mvnfind "${@}"
    • 声明其参数并调用工作函数。 ${sh_io} 尤其重要,因为它保存了函数的返回。 ${sed_sep} 紧随其后;这是一个任意字符串,用于在函数中引用sed 的递归。如果${sed_sep} 设置为可能在您的任何路径名或文件名中找到的值......好吧,不要让它成为。
  • mv -n $1 $2
    • 从头开始移动整个树。它会省去很多麻烦;相信我。您想要做的其余事情 - 重命名 - 只是文件系统元数据的问题。例如,如果您要将它从一个驱动器移动到另一个驱动器,或者跨越任何类型的文件系统边界,那么您最好使用一个命令立即执行此操作。它也更安全。注意为mv 设置的-noclobber 选项;如所写,此函数不会将${SRC_DIR} 放在${TGT_DIR} 已存在的位置。
  • read -R SED &lt;&lt;HEREDOC
    • 我在这里找到了所有 sed 的命令,以节省逃避麻烦并将它们读入一个变量以提供给下面的 sed。解释如下。
  • find . -name ${OLD} -printf
    • 我们开始find 进程。使用find,我们只搜索需要重命名的任何内容,因为我们已经使用函数的第一个命令完成了所有的位置到位置mv 操作。与其对find 采取任何直接操作,例如调用exec,不如使用它通过-printf 动态构建命令行。
  • %dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
    • find 找到我们需要的文件后,它会直接构建并打印(大部分)我们需要处理您的重命名的命令。在每一行的开头添加%dir-depth 将有助于确保我们不会尝试使用尚未重命名的父对象重命名树中的文件或目录。 find 使用各种优化技术来遍历您的文件系统树,并且不确定它是否会以操作安全的顺序返回我们需要的数据。这就是为什么我们接下来...
  • sort -general-numerical -zero-delimited
    • 我们根据%directory-depth 对find 的所有输出进行排序,以便首先处理与${SRC} 关系最近的路径。这避免了涉及mving 文件到不存在的位置的可能错误,并且最大限度地减少了递归循环的需要。 (事实上,你可能很难找到一个循环)
  • sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
    • 我认为这是整个脚本中唯一的循环,并且它只会遍历为每个字符串打印的第二个%Path,以防它包含多个可能需要替换的 ${OLD} 值。我想象的所有其他解决方案都涉及第二个sed 进程,虽然可能不希望短循环,但它肯定优于生成和分叉整个进程。
    • 所以基本上sed 在这里所做的就是搜索${sed_sep},然后,找到它,保存它以及它遇到的所有字符,直到它找到${OLD},然后用${NEW} 替换它。然后它返回到 ${sed_sep} 并再次查找 ${OLD},以防它在字符串中多次出现。如果没有找到,它将修改后的字符串打印到stdout(然后它会再次捕获它)并结束循环。
    • 这避免了必须解析整个字符串,并确保mv 命令字符串的前半部分(当然需要包含 ${OLD})确实包含它,并且后​​半部分被多次更改需要从mv 的目标路径中擦除 ${OLD} 名称。
  • sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
    • 这里的两个-exec 调用在没有第二个fork 的情况下发生。首先,如我们所见,我们根据需要修改find 的-printf 函数命令提供的mv 命令,以正确地将${OLD} 的所有引用更改为${NEW},但是在为此,我们必须使用一些不应包含在最终输出中的任意参考点。所以一旦sed 完成了它需要做的所有事情,我们指示它在传递它之前从保持缓冲区中清除它的参考点。

现在我们回来了

read 将收到如下所示的命令:

% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000

它将read它转换成${msg}作为${sh_io},可以在函数之外随意检查。

酷。

-迈克

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-07-22
    • 1970-01-01
    • 2013-06-05
    • 1970-01-01
    • 2017-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多