Relative paths based on file location instead of current working directory
鉴于:
1 2 3 | some.txt dir |-cat.sh |
其中cat.sh包含以下内容:
1 | cat ../some.txt |
然后在
您要做的是获取脚本的绝对路径(可通过
1 2 3 4 5 | #!/bin/bash parent_path=$( cd"$(dirname"${BASH_SOURCE[0]}")" ; pwd -P ) cd"$parent_path" cat ../some.text |
这将使您的shell脚本独立于从何处调用它。每次运行它时,就好像在
请注意,此脚本仅在您直接调用脚本(即不通过symlink)时才起作用,否则查找脚本的当前位置会变得更复杂一些。
@MartinKonecny的答案提供了正确的答案,但正如他所提到的,只有当实际的脚本没有通过位于不同目录中的符号链接调用时,它才起作用。
这个答案涵盖了这种情况:当通过一个符号链接甚至一个符号链接链调用脚本时,这个解决方案也可以工作:
linux/gnu
如果您的脚本只需要在Linux上运行,或者您知道gnu
1 | scriptDir=$(dirname --"$(readlink -f --"$BASH_SOURCE")") |
注意,GNU
多(类Unix)平台解决方案(包括仅包含POSIX实用程序集的平台):
如果脚本必须在以下任何平台上运行:
有一个
readlink 实用程序,但缺少-f 选项(从GNU的意义上来说,解决其最终目标的符号链接),例如macos。- MacOS使用了较早版本的
readlink 的BSD实现;请注意,freebsd/pc-bsd的最新版本确实支持-f 。
- MacOS使用了较早版本的
甚至没有
readlink ,但有与posix兼容的实用程序,例如HP-UX(谢谢,@charles duffy)。
以下解决方案受https://stackoverflow.com/a/1116890/45375启发,定义helper shell函数
注意:该函数是一个
如果
readlink 可用,则在大多数现代平台上都会使用(无选项)。否则,将解析来自
ls -l 的输出,这是确定符号链接目标的唯一符合POSIX的方法。警告:如果一个文件名或路径包含文字子字符串-> ,这将中断,然而,这是不可能的。(请注意,缺少readlink 的平台仍然可以提供其他非posix方法来解析符号链接;例如,@charles duffy提到了支持%l 格式字符的HP-UX的find 实用程序。由于它的-printf primary;为了简洁起见,函数不尝试检测这种情况。)下面功能的可安装实用程序(脚本)形式(具有附加功能)可以在NPM注册表中找到为
rreadlink ;在Linux和MacOS上,使用[sudo] npm install -g rreadlink 进行安装;在其他平台上(假设它们使用bash 进行安装),请遵循手动安装说明。
如果参数是symlink,则返回最终目标的规范路径;否则,返回参数自己的规范路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #!/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 '"$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 '"${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 '"$(command dirname --"${targetDir}")" else command printf '%s '"${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")") |
只有一条线可以。
1 | cat"`dirname $0`"/../some.txt |