shell 基于文件位置而不是当前工作目录的相对路径[重复]

x7yiwoj4  于 2022-11-16  发布在  Shell
关注(0)|答案(3)|浏览(204)

此问题在此处已有答案

How do I get the directory where a Bash script is located from within the script itself?(74个答案)
八年前就关门了。
给定:

some.txt
dir
 |-cat.sh

cat.sh包含以下内容:

cat ../some.txt

那么在dir中运行./cat.sh可以正常工作,而在与dir相同的级别上运行./dir/cat.sh则不行。我认为这是由于不同的工作目录造成的。有没有简单的方法使路径../some.txt相对于cat.sh的位置?

uelo1irk

uelo1irk1#

您要做的是获取脚本的绝对路径(可通过${BASH_SOURCE[0]}获得),然后在脚本开始时使用该路径获取父目录和cd

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

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

这将使你的shell脚本独立于你从哪里调用它。每次你运行它时,就好像你在dir中运行./cat.sh一样。
请注意,此脚本仅在您直接调用脚本(即不通过符号链接)时有效,否则查找脚本的当前位置会变得有点棘手)

alen0pnh

alen0pnh2#

@ MartinKonecny的答案提供了一个有效的解决方案,但是-正如他提到的-只有当实际脚本不是通过驻留在 * 不同目录 * 中的 * 符号链接 * 调用时,它才起作用。
此答案涵盖了该情况:一个解决方案,当脚本通过 * 符号链接 * 或甚至 * 符号链接链*调用时也能工作:

**Linux / GNU readlink**解决方案:

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

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

请注意,GNU readlink有3个相关的选项,用于将符号链接解析为其最终目标的完整路径:第一个是第五个是第一个。
由于根据定义目标存在于该场景中,因此可以使用3个选项中的任何一个;我在这里选择-f,因为它是最有名的一个。

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

如果脚本必须在满足以下条件的任何平台上运行:

  • 有一个readlink实用程序,但缺少-f选项(在GNU意义上,将符号链接解析到其最终目标)-例如,macOS
  • macOS使用readlink的BSD实现的 * 旧版本 *;请注意,FreeBSD/PC-BSD * 的最新版本支持-f
  • 甚至没有readlink,但具有POSIX兼容实用程序-例如HP-UX(感谢@Charles Duffy)。

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

注意:此函数是一个**bash**函数,并且仅在使用具有POSIX兼容选项的POSIX实用程序时才与POSIX兼容。有关此函数 * 本身 * 用POSIX兼容shell代码编写的版本(对于/bin/sh),请参阅here

  • 如果readlink可用,则使用它(不带选项)-在大多数现代平台上为true。
  • 否则,将解析ls -l的输出,这是确定符号链接目标的唯一符合POSIX的方法。
  • 警告 *:如果文件名或路径包含文字子字符串->,这将中断-但这是不可能的。

(Note缺少readlink的平台仍然可以提供用于解析符号链接的其他非POSIX方法;例如,@ CharlesDuffy提到HP-UX的find实用程序支持%l字符,其-printf主字符;为了简洁起见,该函数不尝试检测此类情况。)

  • 下面的函数(具有附加功能)的可安装 * 实用程序 *(脚本)形式可以作为rreadlink in the npm registry找到;在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'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")")
hgqdbh6s

hgqdbh6s3#

只要一行就可以了。

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

相关问题