Проверка существования подкаталога с помощью 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

В зш

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
}

Theквалификатор glob Nгарантирует, что результатом будет пустой массив, а не ошибка, если совпадений нет. Квалификаторы glob nи Onсортируют список по убыванию номеров версий; настройте это, чтобы выбрать другой элемент при наличии нескольких совпадений.

В Баше:

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
}

В Баше

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

Используйте массив и проверьте, является ли первый элемент массива (т. е. [0] в bash, [1] в zsh) каталогом. Например 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, вам нужно сначала выяснить, какая оболочка запущена в данный момент. Это скажет вам, является ли первый индекс массива 0или 1. Это также определит, нужно ли вам использовать (N)with glob, чтобы сообщить zsh НЕ выходить, если для glob нет совпадений (zsh по умолчанию выйдет при ошибках соответствия glob).

(в этот момент должно стать очевидным, что написание скрипта, который будет надежно работать воба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 могут быть аргументами простых команд или анонимных функций, после 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-/)

Здесь также добавлен Nквалификатор glob, чтобы при отсутствии совпадений glob расширялся до пустого списка вместо сообщения об ошибке, а также -/для ограничения файлов типакаталог(определяется после разрешения символической ссылки с помощью -).

Если существует несколько каталогов gem-файлов Ruby и вы хотите выбрать тот, у которого самый высокий номер версии, вы можете сделать следующее:

() {
  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, и On oупорядочивает в обратном порядке по nимени, поэтому $1будет содержать тот, у которого самый высокий номер.


¹ Как объяснено вinfo zsh 'Conditional Expressions', есть также особый случай, когда extendedglobопция включена, в этом случае вы можете использовать globs внутри [[ ... ]], обычно с операторами -n/ -z. Таким образом, вы можете использовать , [[ -n ~/.gem/ruby/*/bin(#qN-/) ]]чтобы проверить, есть ли здесь хотя бы один такой каталог.

Связанный контент