programing

OS X의 Bash 스크립트 절대 경로

nasanasas 2020. 10. 4. 11:30
반응형

OS X의 Bash 스크립트 절대 경로


OS X에서 현재 실행중인 스크립트의 절대 경로를 얻으려고합니다.

에 대한 많은 답변을 보았습니다 readlink -f $0. 그러나 OS X readlink는 BSD와 동일하기 때문에 작동하지 않습니다 (GNU 버전에서 작동).

이에 대한 기본 솔루션이 있습니까?


거기의 realpath()일을 할 것입니다 C의 기능은,하지만 난 명령 줄에서 사용할 수 아무것도 보이지 않아요. 다음은 빠르고 더러운 교체입니다.

#!/bin/bash

realpath() {
    [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

realpath "$0"

로 시작하는 경우 경로를 그대로 인쇄합니다 /. 그렇지 않은 경우 상대 경로 여야하므로 앞에 추가 $PWD됩니다. #./부분은 오프 스트립 ./의 전면에서 $1.


다음 세 가지 간단한 단계를 통해이 문제와 다른 많은 OS X 문제를 해결할 수 있습니다.

  1. Homebrew 설치
  2. brew install coreutils
  3. grealpath .

(3)으로 변경 될 수 있습니다. realpath(2) 출력 참조


으. 나는 몇 가지 이유로 인해 이전 답변이 약간 필요하다는 것을 발견했습니다. 특히, 그들은 여러 수준의 심볼릭 링크를 해결하지 않으며 매우 "Bash-y"입니다. 원래 질문은 "Bash 스크립트"를 명시 적으로 요구하지만 Mac OS X의 BSD와 유사한 비 GNU에 대해서도 언급 readlink합니다. 그래서 여기에 임의의 수의 심볼릭 링크를 해결하는 합리적인 이식성에 대한 시도가 있습니다 (bash로 'sh'와 dash로 확인했습니다). 유틸리티 자체의 기본 이름에 공백이 있으면 동작을 확신 할 수 없지만 경로의 공백과 함께 작동해야합니다.

#!/bin/sh
realpath() {
  OURPWD=$PWD
  cd "$(dirname "$1")"
  LINK=$(readlink "$(basename "$1")")
  while [ "$LINK" ]; do
    cd "$(dirname "$LINK")"
    LINK=$(readlink "$(basename "$1")")
  done
  REALPATH="$PWD/$(basename "$1")"
  cd "$OURPWD"
  echo "$REALPATH"
}
realpath "$@"

누군가에게 유용 할 수 있기를 바랍니다.


명령 줄 친화적 인 Python 솔루션 변형 :

python -c "import os; print(os.path.realpath('$1'))"

시스템 프로비저닝 스크립트, 즉 Homebrew가 설치되기 전에 실행되는 솔루션을 찾고있었습니다. 적절한 솔루션이 없으면 Perl과 같은 크로스 플랫폼 언어로 작업을 오프로드합니다.

script_abspath=$(perl -e 'use Cwd "abs_path"; print abs_path(@ARGV[0])' -- "$0")

더 자주 우리가 실제로 원하는 것은 포함하는 디렉토리입니다.

here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")

Python을 사용하여 얻으십시오.

#!/usr/bin/env python
import os
import sys

print(os.path.realpath(sys.argv[1]))

가 있기 때문에 realpath 등의 뾰족한 밖으로있다 :

// realpath.c
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char* argv[])
{
  if (argc > 1) {
    for (int argIter = 1; argIter < argc; ++argIter) {
      char *resolved_path_buffer = NULL;
      char *result = realpath(argv[argIter], resolved_path_buffer);

      puts(result);

      if (result != NULL) {
        free(result);
      }
    }
  }

  return 0;
}

Makefile :

#Makefile
OBJ = realpath.o

%.o: %.c
      $(CC) -c -o $@ $< $(CFLAGS)

realpath: $(OBJ)
      gcc -o $@ $^ $(CFLAGS)

그런 다음 다음으로 컴파일 make하고 소프트 링크를 넣습니다.
ln -s $(pwd)/realpath /usr/local/bin/realpath


위에서 볼 수 있듯이 약 6 개월 전에 이것을 촬영했습니다. 나는 비슷한 것을 다시 필요로 할 때까지 그것을 완전히 잊었다. 나는 그것이 얼마나 기초적인지보고 완전히 충격받았다 . 나는 지금까지 약 1 년 동안 꽤 집중적으로 코딩하는 법을 가르쳐 왔지만, 상황이 최악 일 때 전혀 배운 것이없는 것 같은 느낌이 든다.

위의 '솔루션'을 제거하고 싶지만 지난 몇 달 동안 실제로 얼마나 배웠는지에 대한 기록 인 것이 정말 마음에 듭니다.

그러나 나는 탈선한다. 나는 어제 밤에 앉아서 모든 일을했다. 주석의 설명은 충분해야합니다. 내가 계속 작업중인 사본을 추적 하려면이 요점을 따를 수 있습니다. 이것은 아마도 당신이 필요한 것을 할 것입니다.

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi

Mac OS X 용 realpath

realpath() {
    path=`eval echo "$1"`
    folder=$(dirname "$path")
    echo $(cd "$folder"; pwd)/$(basename "$path"); 
}

Example with related path:

realpath "../scripts/test.sh"

Example with home folder

realpath "~/Test/../Test/scripts/test.sh"

On macOS, the only solution that I've found to this that reliably handles symlinks is by using realpath. Since this requires brew install coreutils, I just automated that step. My implementation looks like this:

#!/usr/bin/env bash

set -e

if ! which realpath >&/dev/null; then
  if ! which brew >&/dev/null; then
    msg="ERROR: This script requires brew. See https://brew.sh for installation instructions."
    echo "$(tput setaf 1)$msg$(tput sgr0)" >&2
    exit 1
  fi
  echo "Installing coreutils/realpath"
  brew install coreutils >&/dev/null
fi

thisDir=$( dirname "`realpath "$0"`" )
echo "This script is run from \"$thisDir\""


This errors if they don't have brew installed, but you could alternatively just install that too. I just didn't feel comfortable automating something that curls arbitrary ruby code from the net.

Note that this an automated variation on Oleg Mikheev's answer.


One important test

One good test of any of these solutions is:

  1. put the code in a script file somewhere
  2. in another directory, symlink (ln -s) to that file
  3. run the script from that symlink

솔루션이 심볼릭 링크를 역 참조하고 원래 디렉토리를 제공합니까? 그렇다면 작동합니다.


댓글 작성자와의 의사 소통을 기반으로 나는 그것이 매우 어렵고 실제 경로를 구현하는 세 가지 방법이 우분투와 완전히 동일하게 작동한다는 데 동의했습니다.

그러나 다음 버전은 코너 케이스를 처리 할 수있는 베스트 답변으로는 맥북에서 일상적인 요구를 충족시킬 수 없습니다. 이 코드를 ~ / .bashrc에 넣고 기억하십시오.

  • arg는 하나의 파일 또는 dir 만 될 수 있으며 와일드 카드는 사용할 수 없습니다.
  • dir 또는 파일 이름에 공백이 없습니다.
  • 최소한 파일 또는 디렉토리의 상위 디렉토리가 있습니다.
  • 자유롭게 사용하십시오. .. / 물건, 이것들은 안전 해

    # 1. if is a dir, try cd and pwd
    # 2. if is a file, try cd its parent and concat dir+file
    realpath() {
     [ "$1" = "" ] && return 1

     dir=`dirname "$1"`
     file=`basename "$1"`

     last=`pwd`

     [ -d "$dir" ] && cd $dir || return 1
     if [ -d "$file" ];
     then
       # case 1
       cd $file && pwd || return 1
     else
       # case 2
       echo `pwd`/$file | sed 's/\/\//\//g'
     fi

     cd $last
    }

참고 URL : https://stackoverflow.com/questions/3572030/bash-script-absolute-path-with-os-x

반응형