mkdir、touch 等的 bash/shell 路径名扩展?

mkdir、touch 等的 bash/shell 路径名扩展?

因此,在大多数其他 shell 中执行类似的操作bash将无法在子目录中创建多个子目录或文件...

mkdir */test
touch */hello.txt

当然,实际执行此操作的方法有很多,我首选的方法是在可能的情况下使用find而不是使用for循环,主要是为了可读性。

但我的问题是,为什么上面的方法不起作用?
据我了解,这是因为完整的目标文件/路径不存在,但如果我尝试mkdir或,这肯定是一件好事touch。我总是继续前进,从未真正质疑过它。
但是有人对此有一个体面的解释来帮助我一劳永逸地理解吗?

答案1

您可能忽略的一点是,许多人都遇到麻烦,特别是如果他们在使用 *nix 之前有其他操作系统的经验,那就是,在许多其他操作系统中,通常会传递命令行上的通配符 到命令按其认为合适的方式进行处理。例如,在 Windows 命令提示符中,

rename *.jpeg *.jpg

然而,在 *nix 中,为了简化各个命令(例如 mv)的程序员的工作,通配符(当未引用或转义时)由 shell 以独立于接口和方式处理。通配符作为参数的命令的功能。大多数将文件名作为命令行参数的程序都期望这些文件存在(是的,mkdir并且touch是该规则的例外,如mkfifomknod、 和 在某种程度上,cplnmvrename;并且可能还有其他文件),因此它对于 shell 将通配符扩展为不存在的文件的名称实际上没有意义。对于外壳(我的意思是每一个shell – Bourne、bash、csh、fish、ksh、zsh 等)以不同方式处理异常可能会很麻烦。


也就是说,有几种方法可以获得您想要的结果。如果您知道通配符将扩展为什么,并且它不长,则可以使用大括号扩展来生成它:

touch {red,orange,yellow,green,blue,indigo,violet}/rgb.txt

更通用的解决方案:

sh -c 'for arg do mkdir -- "$arg"/test; done' -- *

吉尔斯帮助我找到了另一种在 bash 中做到这一点的方法。

benbradley=(*)
mkdir "${benbradley[@]/%//test}"

显然benbradley这里只是一个标识符;您可以使用任何名称(例如,任何单个字母)。我花了几次尝试才把它弄好,所以让我把它分解一下:

  • identifier=value 创建一个名为identifier (如果尚不存在)的(标量)变量并value为其分配值。例如,G=Man。您可以使用;引用标量变量例如,,或者,更安全的是, as 。例如,and可能未定义,但is和is 。$identifier$G${identifier}$Gage$Gilla${G}ageManage${G}illaManilla
  • identifier=(value1 value2)创建一个名为的数组变量identifier (如果它尚不存在)并将列出的值分配给它。例如,
      光谱=(红橙黄绿蓝靛紫)
    或者
      all_text_files=(*.txt)
    $name将引用第一个元素(因此相当无用)。您可以将任意元素引用为;例如,is 和is 。您可以使用和 来引用整个数组。${name[subscript]}${spectrum[0]}red${spectrum[4]}blue${name[@]}${name[*]}

所以,这里是碎片:

      “ ${ Benbradley[@] / % / /测试 } "
  • ${parameter/pattern/string} 扩展并替换 的最长匹配项。${parameter}patternstring
  • 如果pattern以 开头#,则它必须在扩展值的开头匹配parameter。如果pattern以 开头%,则它必须在扩展值的末尾匹配parameter。换句话说,
      %(模式的其余部分)
    行为就像
      (正则表达式的其余部分)$
    (是的,这看起来有点倒退)。因此,apattern只是 a% 就像一个正则表达式,它只是 a $– 它匹配输入字符串的末尾(搜索空间)。
  • 我正在使用string一个/test.因此,这将参数的末尾替换为/test (即,它附加/test到参数)。
  • 另一件 不直观的事情是${parameter/pattern/string}总是}以.结尾。不需要,并且不能,以第三个结尾/。因此,一个/string不能被解释为分隔符,因此我们可以有一个string/test 而不需要逃避/.
  • 如果是下标为or 的parameter数组变量,则替换操作将依次应用于数组的每个成员。@*
  • 当您将数组引用为 (而不是)并将结果放在双引号中时,您可以保留数组元素的完整性(即,保留其中的空格和其他特殊字符),而无需将单独的元素组合成一个长单词。${name[@]}${name[*]}

答案2

在调用命令之前,shell 会扩展通配符模式。看G-Man的回答以获得完整的解释。

大多数 shell 需要某种形式的中间命令才能对匹配项应用文本转换。 Zsh 提供了一种即时改变比赛的方法全局限定符:

  • e或者+为每个匹配执行代码,可以通过修改REPLY字符串来改变匹配(甚至可以通过修改数组来改变匹配的数量reply
  • :随后是一个历史扩展修改器应用一些内置转换

例子:

mkdir */(:s:/:/test:)     # takes advantage of the fact that each match ends with /, which it replaces by /test
mkdir *(/e:REPLY+=/test:)

答案3

这不起作用,因为 shell 试图扩展全部的*/test尚不存在的字符串。默认情况下,字符串会扩展为自身。如果您使用 Bash,您可能想查看各种通配选项以您想要的方式处理这个问题。

相关内容