Shell: Относительные пути, основанные на расположении файла вместо текущего рабочего каталога

Дано:

some.txt
dir
 |-cat.sh

С cat.sh с содержимым:

cat ../some.txt

Затем запуск ./cat.sh внутри dir отлично работает при запуске ./dir/cat.sh на том же уровне, что и dir. Я ожидаю, что это будет связано с разными рабочими каталогами. Легко ли было сделать путь ../some.txt относительно местоположения cat.sh?

Ответ 1

Что вы хотите сделать, это получить абсолютный путь к script (доступный через ${BASH_SOURCE[0]}), а затем использовать его, чтобы получить родительский каталог и cd к нему в начале script.

#!/bin/bash
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )

cd "$parent_path"
cat ../some.text

Это сделает вашу оболочку script работать независимо от того, откуда вы ее вызываете. Каждый раз, когда вы запускаете его, он будет, как если бы вы выполняли ./cat.sh внутри dir.

Обратите внимание, что этот script работает только в том случае, если вы вызываете script напрямую (т.е. не через символическую ссылку), в противном случае поиск текущего местоположения script становится немного более сложным)

Ответ 2

@Martin Konecny ​​answer дает правильный ответ, но, как он упоминает, он работает только в том случае, если фактический script не вызывается через символическую ссылку, находящуюся в другой каталог.

Этот ответ охватывает этот случай: решение , которое также работает, когда script вызывается через символическую ссылку или даже цепочку символических ссылок:


Решение Linux/GNU readlink:

Если ваш script должен запускаться только на Linux или вы знаете, что GNU readlink находится в $PATH, используйте readlink -f, что удобно разрешает символическую ссылку на свою конечную цель:

 scriptDir=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")

Обратите внимание, что GNU readlink имеет 3 связанные опции для разрешения символической ссылки на ее конечный полный путь: -f (--canonicalize), -e (--canonicalize-existing) и -m (--canonicalize-missing) - см. man readlink.
Поскольку в этом сценарии существует определение цели по определению, можно использовать любой из 3 вариантов; Я выбрал -f здесь, потому что это самый известный.


Решение для платформы Multi- (Unix-like-) (включая платформы с набором утилит только для POSIX):

Если ваш script должен работать на любой платформе, которая:

  • имеет утилиту readlink, но не имеет опции -f (в смысле GNU для решения символической ссылки на ее конечную цель) - например, macOS.

    • macOS использует более старую версию реализации BSD readlink; обратите внимание, что последние версии FreeBSD/PC-BSD поддерживают -f.
  • даже не имеет readlink, но имеет POSIX-совместимые утилиты - например, HP-UX (спасибо, @Charles Duffy).

Следующее решение, вдохновленное fooobar.com/questions/2795/..., определяет функцию вспомогательной оболочки, rreadlink(), которая разрешает данную символическую ссылку на свою конечную цель в цикле - эта функция , по сути, соответствует реализации POSIX GNU readlink -e option, который похож на параметр -f, за исключением того, что конечная цель должна существовать.

Примечание. Функция является функцией bash и совместима с POSIX только в том смысле, что используются только утилиты POSIX с POSIX-совместимыми опциями. Для версии этой функции, которая сама написана в POSIX-совместимом коде оболочки (для /bin/sh), см. здесь.

  • Если readlink доступен, он используется (без параметров) - true на большинстве современных платформ.

  • В противном случае выводится результат из ls -l, который является единственным способом, совместимым с POSIX, для определения цели символической ссылки.
    Предостережение: это сломается, если имя файла или путь содержит литеральную подстроку -> - что маловероятно, однако.
    (Обратите внимание: платформы, для которых отсутствует readlink, могут по-прежнему предоставлять другие, не-POSIX-методы для решения символической ссылки, например, @Charles Duffy упоминает утилиту HP-UX find, поддерживающую формат %l char с его -printf primary, в интересах краткости функция НЕ пытается обнаружить такие случаи.)

  • Форма функции installable utility (script) ниже (с дополнительной функциональностью) может быть найдена как rreadlink в реестре npm; на Linux и macOS, установите его с помощью [sudo] npm install -g rreadlink; на других платформах (при условии, что они имеют bash), следуйте инструкциям вручную.

Если аргумент является символической ссылкой, возвращается конечный целевой канонический путь; в противном случае возвращается аргумент собственного канонического пути.

#!/usr/bin/env bash

# Helper function.
rreadlink() ( # execute function in a *subshell* to localize the effect of `cd`, ...

  local target=$1 fname targetDir readlinkexe=$(command -v readlink) CDPATH= 

  # Since we'll be using `command` below for a predictable execution
  # environment, we make sure that it has its original meaning.
  { \unalias command; \unset -f command; } &>/dev/null

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [[ -L $target || -e $target ]] || { command printf '%s\n' "$FUNCNAME: ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [[ $fname == '/' ]] && fname='' # !! curiously, `basename /` returns '/'
      if [[ -L $fname ]]; then
        # Extract [next] target path, which is defined
        # relative to the symlink own directory.
        if [[ -n $readlinkexe ]]; then # Use `readlink`.
          target=$("$readlinkexe" -- "$fname")
        else # `readlink` utility not available.
          # Parse `ls -l` output, which, unfortunately, is the only POSIX-compliant 
          # way to determine a symlink target. Hypothetically, this can break with
          # filenames containig literal ' -> ' and embedded newlines.
          target=$(command ls -l -- "$fname")
          target=${target#* -> }
        fi
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we
  # have a normalized path.
  if [[ $fname == '.' ]]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [[ $fname == '..' ]]; then
    # Caveat: something like /var/.. will resolve to /private (assuming
    # /[email protected] -> /private/var), i.e. the '..' is applied AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

# Determine ultimate script dir. using the helper function.
# Note that the helper function returns a canonical path.
scriptDir=$(dirname -- "$(rreadlink "$BASH_SOURCE")")

Ответ 3

Только одна строка будет в порядке.

cat "`dirname $0`"/../some.txt