在脚本中使用 glob 检查子目录是否存在

在脚本中使用 glob 检查子目录是否存在

我正在尝试检查目录是否bin位于有时会更改的目录内。在这种特殊情况下,ruby 的版本号可以更改(例如$HOME/.gem/ruby/2.6.0/bin)。这是我到目前为止所做的:

#!/usr/bin/env zsh
ruby_gem_home="$HOME/.gem/ruby/*/bin"
if [[ -d $ruby_gem_home ]]; then
  echo "The ruby gems directory exists!"
else
  echo "Ruby gems directory missing!"
fi

我不想使用,find因为这是登录过程的一部分。使用内置 zsh/bash 命令来实现此目的最优雅的方法是什么?

谢谢!

编辑:忘了说这是一个zsh脚本。

答案1

针对这种情况专门

我认为寻找任何目录都是没有意义的。使用与已安装的 Ruby 版本不匹配的目录有什么意义?如果用户在 中配置了不同的目录怎么办~/.gemrc

我对 Ruby 不是很熟悉,但我认为正确的解决方案是将gempath.

ruby_gem_home=$(gem environment gempath | sed 's/:.*//')
if [ -z "$ruby_gem_home" ]; then unset ruby_gem_home; fi

在zsh中

function set_ruby_gem_home {
  emulate -LR zsh
  local dirs
  dirs=($HOME/.gem/ruby/*/bin(NnOn))
  if (($#dirs == 0)); then
    unset ruby_gem_home
  else
    ruby_gem_home=$dirs[0]
  fi
}

全局限定符 N确保结果是一个空数组,而不是在不匹配时出现错误。 glob 限定符n并按On版本号递减对列表进行排序;当有多个匹配项时,调整此选项以选择不同的项目。

在bash中:

function set_ruby_gem_home {
  local dirs
  dirs=(~/.gem/ruby/*/bin(NnOn))
  if (($#dirs == 0)); then
    unset ruby_gem_home
  else
    ruby_gem_home=$dirs[0]
  fi
}

在bash中

function set_ruby_gem_home {
  local dirs restore_options
  restore_options=$(setopt +o)
  shopt -s nullglob
  dirs=(~/.gem/ruby/*/bin)
  if (($#dirs == 0)); then
    unset ruby_gem_home
  else
    ruby_gem_home=${dirs[${#dirs[@]}-1]}
  fi
  eval "$restore_options"
}

这里我采用最后一个数组元素,它通常但并不总是最新版本:10在 之前排序9

答案2

使用数组,检查数组的第一个元素(即bash中的[0],zsh中的[1])是否是目录。例如在bash

# the trailing slash in "$HOME"/.gem/ruby/*/bin/ ensures that
# the glob matches only directories.
rubygemdirs=( "$HOME"/.gem/ruby/*/bin/ )

if [ -d "${rubygemdirs[0]}" ] ; then
   echo "At least one ruby gem dir exists"
else
   echo "No ruby gem dirs were found"
fi

zsh要在和中工作bash,您需要首先找出当前正在运行的 shell。这将告诉您数组的第一个索引是否是01。它还将确定您是否需要与 glob 一起使用,(N)以告诉 zsh 在 glob 没有匹配项时不要退出(如果 glob 匹配错误,zsh 将默认退出)。

(此时,编写一个能够可靠工作的脚本应该开始变得明显两个都bash 和 zsh 可能会带来更多的麻烦而不是其价值)

if [ -n "$BASH_VERSION" ] ; then
   first_idx=0
   rubygemdirs=( "$HOME"/.gem/ruby/*/bin/ )
elif [ -n "$ZSH_VERSION"  ] ; then
   first_idx=1
   emulate sh
   rubygemdirs=( "$HOME"/.gem/ruby/*/bin/ )
   emulate zsh
fi

if [ -d "${rubygemdirs[$first_idx]}" ] ; then
...

如果需要,您还可以迭代数组以检查元素是否与特定版本匹配。例如

for d in "${rubygemdirs[@]}" ; do
  [[ $d =~ /2\.6\.0/ ]] && echo found gem dir for ruby 2.6.0
done

注意:/正则表达式中的 s 是原义正斜杠。它们不会像 中那样标记正则表达式的开头和结尾sed

答案3

通配符仅在列表上下文中扩展,也就是说,它们可以扩展为多个内容。在对标量变量进行赋值时,它们无法扩展,因为标量变量只能保存一个值。

使用ruby_gem_home="$HOME/.gem/ruby/*/bin",您只需将/home/user/.gem/ruby/*/bin字符串存储到$ruby_gem_home标量变量中,而不是 glob 在列表上下文中扩展为的文件列表。

zsh 中的列表上下文可以是简单命令或匿名函数的参数, after for/select x in,在重定向的目标中(当multios打开时),在数组或关联数组分配以及其他一些极端情况中。

因此,在这里,您可以使用匿名函数:

() {
  unset ruby_gem_home
  case $# in
    (0) print -u2 "Ruby gems directory missing!";;
    (1) print -u2 "The ruby gems directory exists!"
        ruby_gem_home=$1;;
    (*) print -u2 "More than one ruby gem directory, don't know which to choose!";;
  esac
} ~/.gem/ruby/*/bin(N-/)

这里还添加了Nglob 限定符,以便在没有匹配项时 glob 扩展为空列表而不是报告错误,并-/限制为文件类型目录(在符号链接解析后确定-)。

如果有多个 ruby​​ gem 目录,并且您想选择版本号最高的一个,您可以执行以下操作:

() {
  unset ruby_gem_home
  case $# in
    (0) print -u2 "Ruby gems directory missing!";;
    (1) print -u2 "The ruby gems directory exists!"
        ruby_gem_home=$1;;
    (*) print -ru2 "More than one ruby gem directory, picking $1!"
        ruby_gem_home=$1;;
  esac
} ~/.gem/ruby/*/bin(N-/nOn)

其中n打开numericglobsort,并按ameOn o反向排序n,因此$1将包含数字最高的那个。


1 正如中所解释的info zsh 'Conditional Expressions',还有一种特殊情况,当该extendedglob选项打开时,您可以在内部使用 use globs [[ ... ]],通常与-n/-z运算符一起使用。所以你可以用来[[ -n ~/.gem/ruby/*/bin(#qN-/) ]]检查这里是否至少有一个这样的目录。

相关内容