bash 基于文件位置而不是当前工作目录的相对路径

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24112727/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 00:52:37  来源:igfitidea点击:

Relative paths based on file location instead of current working directory

bashshell

提问by ben

Given:

鉴于:

some.txt
dir
 |-cat.sh

With cat.sh having the content:

cat.sh 具有以下内容:

cat ../some.txt

Then running ./cat.shinside dirworks fine while running ./dir/cat.shon the same level as dirdoes not. I expect this to be due the different working directories. Is there an easy was to make the path ../some.txtrelative to the location of cat.sh?

然后在./cat.sh内部运行时dir可以正常运行./dir/cat.sh,而在同一级别上运行dir时却没有。我希望这是由于不同的工作目录。是否有一种简单的方法可以使路径../some.txt相对于 的位置cat.sh

回答by Martin Konecny

What you want to do is get the absolute path of the script (available via ${BASH_SOURCE[0]}) and then use this to get the parent directory and cdto it at the beginning of the script.

您想要做的是获取脚本的绝对路径(可通过${BASH_SOURCE[0]}),然后使用它来获取父目录并cd在脚本的开头访问它。

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

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

This will make your shell script work independent of where you invoke it from. Each time you run it, it will be as if you were running ./cat.shinside dir.

这将使您的 shell 脚本独立于您从何处调用它。每次运行它,就像在./cat.sh里面运行一样dir

Note that this script only works if you're invoking the script directly (i.e. not via a symlink), otherwise the finding the current location of the script gets a little more tricky)

请注意,此脚本仅在您直接调用脚本(即不通过符号链接)时才有效,否则查找脚本的当前位置会变得更加棘手)

回答by mklement0

@Martin Konecny's answerprovides the correct answer, but - as he mentions - it only works if the actual script is not invoked through a symlinkresiding in a different directory.

@Martin Konecny 的回答提供了正确的答案,但是 - 正如他所提到的 - 只有当实际脚本不是通过驻留在不同目录中符号链接调用时才有效。

This answer covers that case: a solution that also works when the script is invoked through a symlinkor even a chain of symlinks:

这个答案涵盖了这种情况:当通过符号链接甚至符号链接链调用脚本时也可以使用的解决方案



Linux / GNU readlinksolution:

Linux/GNUreadlink解决方案:

If your script needs to run on Linuxonly or you know that GNU readlinkis in the $PATH, use readlink -f, which conveniently resolves a symlink to its ultimatetarget:

如果您的脚本只需要在Linux上运行,或者您知道GNUreadlink在 中$PATH,请使用readlink -f,它可以方便地将符号链接解析到其最终目标:

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

Note that GNU readlinkhas 3 related options for resolving a symlink to its ultimate target's full path: -f(--canonicalize), -e(--canonicalize-existing), and -m(--canonicalize-missing) - see man readlink.
Since the target by definition exists in this scenario, any of the 3 options can be used; I've chosen -fhere, because it is the most well-known one.

需要注意的是GNUreadlink具有用于消除符号链接到其最终目标的完整路径3级相关的选项:-f--canonicalize), (-e--canonicalize-existing-m--canonicalize-missing) -见man readlink
由于此场景中存在定义的目标,因此可以使用 3 个选项中的任何一个;我选择-f这里,因为它是最知名的。



Multi-(Unix-like-)platform solution(including platforms with a POSIX-onlyset of utilities):

多(类 Unix)平台解决方案(包括具有仅 POSIX实用程序集的平台):

If your script must run on any platform that:

如果您的脚本必须在以下任何平台上运行:

  • has a readlinkutility, but lacks the -foption (in the GNU sense of resolving a symlink to its ultimate target) - e.g., macOS.

    • macOS uses an older versionof the BSD implementation of readlink; note that recent versions of FreeBSD/PC-BSD dosupport -f.
  • does not even have readlink, but has POSIX-compatibleutilities - e.g., HP-UX(thanks, @Charles Duffy).

  • 有一个readlink实用程序,但缺乏-f选项(在 GNU 意义上将符号链接解析为其最终目标) - 例如,macOS

    • macOS 使用旧版本的 BSD 实现readlink;请注意,最新版本的 FreeBSD/PC-BSD确实支持-f.
  • 甚至没有readlink,但具有与POSIX 兼容的实用程序 - 例如,HP-UX(谢谢,@Charles Duffy)。

The following solution, inspired by https://stackoverflow.com/a/1116890/45375, defines helper shell function, rreadlink(), which resolves a given symlink to its ultimate target in a loop - this function is in effect a POSIX-compliant implementation of GNU readlink's -eoption, which is similar to the -foption, except that the ultimate target must exist.

以下解决方案受https://stackoverflow.com/a/1116890/45375 的启发,定义了辅助 shell 函数 ,rreadlink()函数将给定的符号链接解析为循环中的最终目标 - 该函数实际上是符合 POSIX 的实现GNUreadlink-e选项-f选项类似,只是最终目标必须存在

Note: The function is a bashfunction, and is POSIX-compliant only in the sense that only POSIX utilities with POSIX-compliant options are used. For a version of this function that is itselfwritten in POSIX-compliant shell code (for /bin/sh), see here.

注意:该函数是一个bash函数,并且仅在仅使用具有 POSIX 兼容选项的 POSIX 实用程序的意义上才符合 POSIX。有关此函数本身以符合 POSIX 标准的 shell 代码(用于/bin/sh)编写的版本,请参见此处

  • If readlinkis available, it is used (without options) - true on most modern platforms.

  • Otherwise, the output from ls -lis parsed, which is the only POSIX-compliant way to determine a symlink's target.
    Caveat: this will break if a filename or path contains the literal substring ->- which is unlikely, however.
    (Note that platforms that lack readlinkmay still provide other, non-POSIX methods for resolving a symlink; e.g., @Charles Duffy mentions HP-UX's findutility supporting the %lformat char. with its -printfprimary; in the interest of brevity the function does NOT try to detect such cases.)

  • An installable utility(script) form of the function below (with additional functionality) can be found as rreadlinkin the npm registry; on Linux and macOS, install it with [sudo] npm install -g rreadlink; on other platforms (assuming they have bash), follow the manual installation instructions.

  • 如果readlink可用,则使用(不带选项) - 在大多数现代平台上为 true。

  • 否则,将ls -l解析来自的输出,这是确定符号链接目标的唯一符合 POSIX 的方法。
    警告:如果文件名或路径包含文字子字符串->,这将中断- 然而,这不太可能。
    (请注意,缺少的平台readlink可能仍会提供其他非 POSIX 方法来解析符号链接;例如,@Charles Duffy 提到 HP-UX 的find实用程序支持%l格式 char. 及其-printf主要格式;为了简洁起见,该函数不会尝试检测此类情况。)

  • 可以在 npm 注册表中找到以下函数的可安装实用程序(脚本)形式(具有附加功能);在 Linux 和 macOS 上,安装它;在其他平台上(假设有),请按照手动安装说明进行操作rreadlink[sudo] npm install -g rreadlinkbash

If the argument is a symlink, the ultimate target's canonical path is returned; otherwise, the argument's own canonical path is returned.

如果参数是符号链接,则返回最终目标的规范路径;否则,返回参数自己的规范路径。

#!/usr/bin/env bash

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

  local target= 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's 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's 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's 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
    # /var@ -> /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")")

回答by Nancy

Just one line will be OK.

只要一行就可以了。

cat "`dirname ##代码##`"/../some.txt