【问题标题】:Bash one-liner for isolating directory name用于隔离目录名称的 Bash one-liner
【发布时间】:2017-01-19 23:25:00
【问题描述】:

在 Bash,v.4.3.11(1) 我有这个示例代码:

#!/bin/bash

dir=("/home/user/Documents/" "/home/user/Music/" "/home/user/Videos/" \
"/home/user/Photos/")

baseDir="/home/user/"

for i in "${!dir[@]}"
do
    niceName=${dir[$i]#"$baseDir"}  # removes baseDir from front part
    printf "%s\n" "${niceName%"/"}"  # also removes trailing slash from end
done

有没有办法将两个命令合二为一,并且在 for 循环中只有 printf? (最好不要诉诸 awk 或 sed,但如果不可避免,也可以)。

我尝试了各种组合,但最终出现“错误替换”错误。 例如,printf "%s\n" "${niceName=${dir[$i]#"$baseDir"%"/"}" 不适合我。

【问题讨论】:

  • 您有什么理由使用for i in "${!dir[@]}"${dir[$i]} 而不是for i in "${dir[@]}" 和只是$i
  • 数组定义中不需要反斜杠;换行符和空格一样可以分隔数组元素。
  • Benjamin W.:需要使索引易于获得,但在一般情况下你是对的。 Chepner:有用的观察,谢谢!

标签: bash string-substitution


【解决方案1】:

这是一个更简单的版本。

#!/bin/bash
dir=("/home/user/Documents/" "/home/user/Music/" "/home/user/Videos/" "/home/user/Photos/")

for d in "${dir[@]}"
do
  basename "$d"
done

请注意basename的参数需要加引号,否则带有一些字符(如空格)的目录名会导致问题。

循环的内部也可以被下面的基于内置的解决方案替换(更快并且没有外部依赖):

  d="${d%/}"
  echo "${d##*/}"

【讨论】:

  • basename 与类似的参数扩展相比效率很低。 ${d##*/} 的计算速度要快得多, 因为它内置在 shell 中,而 basename 是一个单独的可执行文件。
  • 它的执行速度要快得多,并且在速度很重要的情况下或对于从记忆中编写此内容的知识渊博的人来说肯定是正确的选择。从这个意义上说,它更好。虽然它更难记住,所以对于大多数人来说,最容易记住的解决方案可能会更快,一旦你包括他们需要查找它的时间。
  • 如果一个人已经记住这一事实就足以成为保持一个坏习惯的充分理由,那么这个习惯就永远不会被改掉。性能只是一个方面——另一个方面是健壮性:如果 PATH 无效、LD_LIBRARY_PATH 错误,或者如果在已被剥离到骨头的嵌入式平台上运行,Shell 内置程序不会失败。我的大部分不满是,shell 脚本以天生缓慢的名声而著称,这更多地归功于普遍接受的习惯用法和实践,而不是技术限制。如果您查看 systemd 的理由,“shell 脚本效率低下”是很重要的一部分。
  • ...现在,bash 一个效率低下的解释器,但并非所有解释器都是:ksh93 在运行设计良好的脚本时非常快(不需要子shell或外部命令)。但是,如果没有人编写遵循良好实践的脚本,那么给定解释器的效率就无关紧要了:天生无法有效解释的脚本就是这样,而那些越普遍是,我们越是看到 shell 脚本的名声比它应得的还要糟糕。
  • 所以——我在这里尽我所能鼓励人们学习健壮和高效的习语(和安全——损害健壮性的做法也经常有安全隐患),而且相当相信在这样做的过程中,我会以某种微小的方式让世界变得更美好。 :)
【解决方案2】:

如果您正在寻找使用替换的单线,这将起作用:

dir=("/home/user/Documents/" "/home/user/Music/" "/home/user/Videos/" \
"/home/user/Photos/")
baseDir="/home/user/"
dir=("${dir[@]%/}")    ## This line removes trailing forward slashes
printf "%s\n" "${dir[@]#$baseDir}"

【讨论】:

  • 顺便说一句,OP 使用printf '%s\n' 而不是echo 在这里可能是一个很好的调用——它区分了foo bar"foo bar"。这个答案正确的,但不能从 echo 的输出中明确地确定这一点,因为在 "${dir[*]}"-style 和 "${dir[@]}"-style 扩展案例之间无法区分输出。
  • 你是对的!我忽略了这一点并对其进行了编辑以使其行为正常并将每个放在自己的行上。此外,如果出于某种原因您希望它与 echo 一起使用,您可以使用 (IFS=$'\n'; echo "${dir[*]#$baseDir}") 打印它们来获得相同的功能。
  • 您的代码没有为我删除尾部斜杠。
  • @knedas 我现在已经更新它以删除尾部斜杠
【解决方案3】:

您可以使用basename,它只返回基本文件名。在这种情况下,您的文件就是目录。

#!/bin/bash
dir=("/home/user/Documents/" "/home/user/Music/" "/home/user/Videos/" "/home/user/Photos/")

for i in "${!dir[@]}"
do
  echo $( basename ${dir[$i]})
done

【讨论】:

  • 使用命令替换 $( ) 来捕获命令的输出并且在该输出上“回显”是多余的。 “basename”已经回显了它的输出。此外,需要引用 basename 的参数。
  • +1 建议 basename 但 Fred 对更正的看法是正确的,而且这也没有解决双参数扩展的一般问题,因为它仅适用于 dirs。
【解决方案4】:

据我所知,bash 不处理“双参数扩展”(我相信 zsh 可以)。但是,您可以拼凑出这样的解决方案:

dir=("/home/user/Documents/" "/home/user/Music/" "/home/user/Videos/" "/home/user/Photos/")
trunk=/home/user/

echo $(dir=( "${dir[@]##${trunk}}" ); echo "${dir[@]%/}")

【讨论】:

  • 引号很重要 -- dir=( ${foo[@]} )dir=( "${foo[@]}" ) 不同 -- 使用带有空格的名称。
  • ...顺便说一句,“名称有空格”(或换行符,或可能是任何其他非 NUL 字符)是这种方法使用不安全的原因:唯一可以安全地保留数组的方法与所有可能的文件名一起使用的相互分离的元素是对它们进行 NUL 分隔,但命令替换中的 NUL 会被静默删除。
  • (请注意,“所有可能的文件名”甚至包括换行符,因此您也不能相信这些)。
  • 将其修改为 printf "%s\n" $(dir=( "${dir[$i]##${baseDir}}" ); echo "${dir[@]%/}" 以适应示例(每行一个条目),但当 dirs 有空格时无法按预期工作。但是,感谢您让我知道 bash 不处理双参数扩展。这是我的问题的根本问题。
猜你喜欢
  • 2016-04-22
  • 1970-01-01
  • 1970-01-01
  • 2014-03-20
  • 2018-04-05
  • 1970-01-01
  • 1970-01-01
  • 2013-07-22
  • 2013-09-02
相关资源
最近更新 更多