Абсолютный путь сценария Bash в OS X

99

Я пытаюсь получить абсолютный путь к текущему сценарию в OS X.

Я видел много ответов readlink -f $0. Однако, поскольку OS X readlinkтакая же, как BSD, она просто не работает (работает с версией GNU).

Есть ли готовое решение для этого?

Могучая резиновая уточка
источник

Ответы:

91

Есть realpath()функция C, которая выполнит эту работу, но я не вижу ничего доступного в командной строке. Вот быстрая и грязная замена:

#!/bin/bash

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

realpath "$0"

Это напечатает путь дословно, если он начинается с /. Если нет, это должен быть относительный путь, поэтому он $PWDначинается впереди. #./Часть отсекает ./от передней части $1.

Джон Кугельман
источник
1
Я также заметил функцию C, но не смог найти соответствующий двоичный файл или что-то еще. В любом случае, ваша функция отлично подходит для моих нужд. Спасибо!
The Mighty Rubber Duck
7
Обратите внимание, что это не разыменовывает символические ссылки.
Адам Ванденберг
17
realpath ../somethingвозвращается$PWD/../something
Пт
У меня это сработало, но я переименовал его в «realpath_osx ()», так как мне МОЖЕТ потребоваться отправить этот сценарий bash в оболочку Linux, и я не хочу, чтобы он конфликтовал с «настоящим» реальным путем! (Я уверен, что есть более элегантный способ, но я bash n00b.)
Эндрю Текен
8
command -v realpath >/dev/null 2>&1 || realpath() { ... }
Кара Брайтвелл
116

Эти три простых шага решат эту и многие другие проблемы OS X:

  1. Установить Homebrew
  2. brew install coreutils
  3. grealpath .

(3) можно заменить на просто realpath , см. (2) вывод

Олег Михеев
источник
3
Это решает проблему, но требует установки того, что вам может не понадобиться или которое может не быть в целевой системе, например OS X
Джейсон С.
7
@JasonS GNU Coreutils слишком хорош, чтобы не использовать. Он включает в себя множество замечательных утилит. На самом деле это так хорошо, что ядро ​​Linux бесполезно без него, и почему некоторые люди называют его GNU / Linux. Coreutils великолепен. отличный ответ это должен быть выбранный ответ.
7
@omouse В вопросе конкретно упоминается «нестандартное решение» (для OS X). Независимо от того, насколько хорош coreutils, он не является «из коробки» в OS X, так что ответ плохой. Вопрос не в том, насколько хорош coreutils или лучше ли он, чем то, что есть в OS X. Есть готовые решения, которые $( cd "$(dirname "$0")" ; pwd -P )мне подходят .
Jason S
2
Одна из «особенностей» OS X заключается в том, что каталог и имена файлов не чувствительны к регистру. В результате, если у вас есть каталог, который называется, XXXи кто- cd xxxто pwdвернется .../xxx. За исключением этого ответа, все вышеперечисленные решения возвращаются xxxтогда, когда вы действительно хотите XXX. Спасибо!
Эндрю
1
Я не знаю, чем бесконечное копирование, вставка и переосмысление кода лучше, чем существующее решение для диспетчера пакетов. Если вам нужно realpath, то что произойдет, если вам почти наверняка понадобятся другие предметы coreutils? Переписать и эти функции в bash? : P
Иезекииль Виктор
28

Тьфу. Я обнаружил, что предыдущие ответы немного не подходят по нескольким причинам: в частности, они не разрешают несколько уровней символических ссылок, и они чрезвычайно "Bash-y". Хотя в исходном вопросе явно содержится запрос на «сценарий Bash», в нем также упоминается BSD-подобный Mac OS X, отличный от GNU readlink. Итак, вот попытка некоторой разумной переносимости (я проверил это с помощью bash как 'sh' и тире), разрешая произвольное количество символических ссылок; и он также должен работать с пробелами в пути (ах), хотя я не уверен в поведении, если есть пробелы в базовом имени самой утилиты, так что, может быть, гм, избежать этого?

#!/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 "$@"

Надеюсь, что это может быть кому-то полезно.

Джефф Никсон
источник
1
Я бы рекомендовал использовать только localпеременные, определенные внутри функции, чтобы не загрязнять глобальное пространство имен. Напр local OURPWD=.... Работает по крайней мере для bash.
Майкл Пэсолд
Кроме того, в коде не следует использовать заглавные буквы для частных переменных. Переменные в верхнем регистре зарезервированы для использования системой.
tripleee
Спасибо за сценарий. В случае, если ссылка (и) и реальный файл имеют разные базовые имена, вероятно, было бы неплохо добавить BASENAME=$(basename "$LINK") внутри while и использовать его во втором установщике LINK и установщике REALPATH
stroborobo
Это не обрабатывает символические ..ссылки и родительские ссылки, как это realpathбыло бы. С доморощенным coreutilsустановлено, попробуйте ln -s /var/log /tmp/linkexampleтогда realpath /tmp/linkexample/../; это печатает /private/var. Но /tmp/linkexample/..вместо этого ваша функция производит , потому что ..это не символическая ссылка.
Мартейн Питерс
10

Вариант решения Python, более удобный для командной строки:

python -c "import os; print(os.path.realpath('$1'))"
нанав йорбиз
источник
2
На всякий случай, если кто-то достаточно сумасшедший, чтобы запустить интерпретатор Python для одной команды…
Бахзау
Я был достаточно зол, но использовалpython -c "import os; import sys; print(os.path.realpath(sys.argv[1]))"
Алекс Чемберлен
7

Я искал решение для использования в сценарии подготовки системы, то есть запускать его до того, как 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")
4ae1e1
источник
Большой! Perl хорош из-за небольших накладных расходов! Вероятно, вы могли бы упростить первую версию до FULLPATH=$(perl -e "use Cwd 'abs_path'; print abs_path('$0')"). Есть основания против?
F Pereira
@FPereira Создание программного кода из неэкранированной строки, введенной пользователем, никогда не является хорошей идеей. ''не пуленепробиваемый. $0Например, он ломается, если содержит одинарную кавычку. Очень простой пример: попробуйте свою версию /tmp/'/test.shи вызовите /tmp/'/test.shее по полному пути.
4ae1e1
Или еще проще, /tmp/'.sh.
4ae1e1
6

Поскольку существует реальный путь, как указывали другие:

// 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

ВафлиСуфле
источник
мы могли бы просто сделать gcc realpath.c -o /usr/local/bin/realpath?
Alexander Mills
1
@AlexanderMills Вы не должны запускать компилятор от имени пользователя root; а затем , если вы этого не сделаете, вы не будете иметь права на запись/usr/local/bin
tripleee
6

Используйте Python, чтобы получить это:

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

print(os.path.realpath(sys.argv[1]))
разгромный
источник
2
abs_path () {    
   echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
}

dirnameдаст имя каталога /path/to/file, то есть /path/to.

cd /path/to; pwd гарантирует, что путь является абсолютным.

basenameдаст только имя файла в /path/to/file, то есть file.

Логу
источник
Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, почему и / или как этот код отвечает на вопрос, улучшает его долгосрочную ценность.
Игорь Ф.
1

Как вы можете видеть выше, я попробовал это около 6 месяцев назад. Я совершенно забыл об этом, пока не обнаружил, что снова нуждаюсь в подобном. Я был полностью шокирован увидев, насколько это было примитивно; Я уже около года довольно интенсивно учу себя программировать, но мне часто кажется, что я вообще ничего не научился, когда дела идут хуже всего.

Я бы удалил указанное выше «решение», но мне действительно нравится, что оно является своего рода записью того, сколько я действительно узнал за последние несколько месяцев.

Но я отвлекся. Я сел и разобрался со всем этим вчера вечером. Объяснения в комментариях должно быть достаточно. Если вы хотите отследить копию, над которой я продолжаю работать, вы можете следовать этой сути. Вероятно, это то, что вам нужно.

#!/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
Джефф Никсон
источник
1
Основная ссылка кажется неработающей.
Alex
этот ответ работает: stackoverflow.com/a/46772980/1223975 .... вы должны просто использовать это.
Alexander Mills
Обратите внимание, что ваша реализация не разрешает символические ..ссылки перед родительскими ссылками; например /foo/link_to_other_directory/.., разрешается как /fooродительский элемент любого пути, на который /foo/link_to_other_directoryуказывает символическая ссылка . readlink -fи realpathразрешить каждый компонент пути, начиная с корня и обновляя добавляемые целевые объекты ссылки до остальных, которые все еще обрабатываются. Я добавил ответ на этот вопрос, заново реализуя эту логику.
Мартейн Питерс
1

realpath для Mac OS X

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

Пример со связанным путем:

realpath "../scripts/test.sh"

Пример с домашней папкой

realpath "~/Test/../Test/scripts/test.sh"
Евгений Карпов
источник
1
хорошее простое решение, я только что нашел одно предостережение, когда его ..if test -d $path ; then echo $(cd "$path"; pwd) ; else [...]
вызов
Не работает для меня, тогда "$(dirname $(dirname $(realpath $0)))"как работает, поэтому нужно что-то еще ...
Александр Миллс
Бесполезно использованиеecho также не должно быть совершенно.
tripleee
На самом деле это не разрешает символические ссылки так, как это сделал бы realpath. Он разрешает ..родительские ссылки до разрешения символических ссылок, а не после; попробуйте это с coreutilsустановленным homebrew , создайте ссылку, ln -s /var/log /tmp/linkexampleзатем запустите realpath /tmp/linkexample/../; это печатает /private/var. Но /tmp/linkexample/..вместо этого ваша функция производит , потому что pwdвсе еще отображается /tmp/linkexampleпосле компакт-диска.
Мартейн Питерс
1

В macOS я нашел единственное решение, которое надежно обрабатывает символические ссылки - это использование realpath. Поскольку для этого требуется brew install coreutils, я просто автоматизировал этот шаг. Моя реализация выглядит так:

#!/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\""


Это ошибки, если их нет brew установлены, но вы также можете просто установить это. Мне просто неудобно автоматизировать что-то, что извлекает произвольный код Ruby из сети.

Обратите внимание, что это автоматизированный вариант ответа Олега Михеева .


Один важный тест

Хороший тест любого из этих решений:

  1. поместите код в файл сценария где-нибудь
  2. в другом каталоге, символическая ссылка ( ln -s) на этот файл
  3. запустить скрипт из этой символической ссылки

Разыменовывает ли решение символическую ссылку и дает ли вам исходный каталог? Если да, то это работает.

Глиняные мосты
источник
0

Кажется, это работает для OSX, не требует никаких двоичных файлов и было извлечено отсюда

function normpath() {
  # Remove all /./ sequences.
  local path=${1//\/.\//\/}

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}
Брэд Паркс
источник
0

Мне это нравится:

#!/usr/bin/env bash
function realpath() {
    local _X="$PWD"
    local _LNK=$1
    cd "$(dirname "$_LNK")"
    if [ -h "$_LNK" ]; then
        _LNK="$(readlink "$_LNK")"
        cd "$(dirname "$_LNK")"
    fi
    echo "$PWD/$(basename "$_LNK")"
    cd "$_X"
}
dcmorse
источник
0

Мне нужна была realpathзамена OS X, которая правильно работает с путями с символическими ссылками и родительскими ссылками, как и если readlink -fбы . Это включает разрешение символических ссылок в пути перед разрешением родительских ссылок; например, если вы установили coreutilsбутылку для домашнего пивоварения , запустите:

$ ln -s /var/log/cups /tmp/linkeddir  # symlink to another directory
$ greadlink -f /tmp/linkeddir/..      # canonical path of the link parent
/private/var/log

Обратите внимание, что readlink -fэто разрешено /tmp/linkeddir до разрешения ..ссылки на родительский каталог. Конечно, readlink -fна Mac тоже нет .

Итак, как часть реализации bash, realpathя повторно реализовал то, что делает canonicalize_filename_mode(path, CAN_ALL_BUT_LAST)вызов функции GNUlib , в Bash 3.2; это также вызов функции, который readlink -fделает GNU :

# shellcheck shell=bash
set -euo pipefail

_contains() {
    # return true if first argument is present in the other arguments
    local elem value

    value="$1"
    shift

    for elem in "$@"; do 
        if [[ $elem == "$value" ]]; then
            return 0
        fi
    done
    return 1
}

_canonicalize_filename_mode() {
    # resolve any symlink targets, GNU readlink -f style
    # where every path component except the last should exist and is
    # resolved if it is a symlink. This is essentially a re-implementation
    # of canonicalize_filename_mode(path, CAN_ALL_BUT_LAST).
    # takes the path to canonicalize as first argument

    local path result component seen
    seen=()
    path="$1"
    result="/"
    if [[ $path != /* ]]; then  # add in current working dir if relative
        result="$PWD"
    fi
    while [[ -n $path ]]; do
        component="${path%%/*}"
        case "$component" in
            '') # empty because it started with /
                path="${path:1}" ;;
            .)  # ./ current directory, do nothing
                path="${path:1}" ;;
            ..) # ../ parent directory
                if [[ $result != "/" ]]; then  # not at the root?
                    result="${result%/*}"      # then remove one element from the path
                fi
                path="${path:2}" ;;
            *)
                # add this component to the result, remove from path
                if [[ $result != */ ]]; then
                    result="$result/"
                fi
                result="$result$component"
                path="${path:${#component}}"
                # element must exist, unless this is the final component
                if [[ $path =~ [^/] && ! -e $result ]]; then
                    echo "$1: No such file or directory" >&2
                    return 1
                fi
                # if the result is a link, prefix it to the path, to continue resolving
                if [[ -L $result ]]; then
                    if _contains "$result" "${seen[@]+"${seen[@]}"}"; then
                        # we've seen this link before, abort
                        echo "$1: Too many levels of symbolic links" >&2
                        return 1
                    fi
                    seen+=("$result")
                    path="$(readlink "$result")$path"
                    if [[ $path = /* ]]; then
                        # if the link is absolute, restart the result from /
                        result="/"
                    elif [[ $result != "/" ]]; then
                        # otherwise remove the basename of the link from the result
                        result="${result%/*}"
                    fi
                elif [[ $path =~ [^/] && ! -d $result ]]; then
                    # otherwise all but the last element must be a dir
                    echo "$1: Not a directory" >&2
                    return 1
                fi
                ;;
        esac
    done
    echo "$result"
}

Он включает в себя обнаружение циклических символических ссылок, выходящих, если один и тот же (промежуточный) путь встречается дважды.

Если все, что вам нужно readlink -f, это можно использовать как:

readlink() {
    if [[ $1 != -f ]]; then  # poor-man's option parsing
        # delegate to the standard readlink command
        command readlink "$@"
        return
    fi

    local path result seenerr
    shift
    seenerr=
    for path in "$@"; do
        # by default readlink suppresses error messages
        if ! result=$(_canonicalize_filename_mode "$path" 2>/dev/null); then
            seenerr=1
            continue
        fi
        echo "$result"
    done
    if [[ $seenerr ]]; then
        return 1;
    fi
}

Для realpath, я также нужен --relative-toи --relative-baseподдержки, которые дают вам относительные пути после канонизации:

_realpath() {
    # GNU realpath replacement for bash 3.2 (OS X)
    # accepts --relative-to= and --relative-base options
    # and produces canonical (relative or absolute) paths for each
    # argument on stdout, errors on stderr, and returns 0 on success
    # and 1 if at least 1 path triggered an error.

    local relative_to relative_base seenerr path

    relative_to=
    relative_base=
    seenerr=

    while [[ $# -gt 0 ]]; do
        case $1 in
            "--relative-to="*)
                relative_to=$(_canonicalize_filename_mode "${1#*=}")
                shift 1;;
            "--relative-base="*)
                relative_base=$(_canonicalize_filename_mode "${1#*=}")
                shift 1;;
            *)
                break;;
        esac
    done

    if [[
        -n $relative_to
        && -n $relative_base
        && ${relative_to#${relative_base}/} == "$relative_to"
    ]]; then
        # relative_to is not a subdir of relative_base -> ignore both
        relative_to=
        relative_base=
    elif [[ -z $relative_to && -n $relative_base ]]; then
        # if relative_to has not been set but relative_base has, then
        # set relative_to from relative_base, simplifies logic later on
        relative_to="$relative_base"
    fi

    for path in "$@"; do
        if ! real=$(_canonicalize_filename_mode "$path"); then
            seenerr=1
            continue
        fi

        # make path relative if so required
        if [[
            -n $relative_to
            && ( # path must not be outside relative_base to be made relative
                -z $relative_base || ${real#${relative_base}/} != "$real"
            )
        ]]; then
            local common_part parentrefs

            common_part="$relative_to"
            parentrefs=
            while [[ ${real#${common_part}/} == "$real" ]]; do
                common_part="$(dirname "$common_part")"
                parentrefs="..${parentrefs:+/$parentrefs}"
            done

            if [[ $common_part != "/" ]]; then
                real="${parentrefs:+${parentrefs}/}${real#${common_part}/}"
            fi
        fi

        echo "$real"
    done
    if [[ $seenerr ]]; then
        return 1
    fi
}

if ! command -v realpath > /dev/null 2>&1; then
    # realpath is not available on OSX unless you install the `coreutils` brew
    realpath() { _realpath "$@"; }
fi

Я включил модульные тесты в свой запрос на проверку кода для этого кода .

Мартейн Питерс
источник
-2

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

Но следующая версия может обрабатывать угловые случаи, лучший ответ не может и удовлетворить мои повседневные потребности на MacBook. Поместите этот код в свой ~ / .bashrc и запомните:

  • arg может быть только 1 файлом или 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
    }
Occia
источник
Вы хотите избежать бесполезного использованияecho . Просто pwdделает то же самое, echo $(pwd)не имея на нерест второй копии оболочки. Кроме того, отсутствие цитирования аргумента echoявляется ошибкой (вы потеряете все начальные или конечные пробелы, любые смежные внутренние символы пробелов, расширенные подстановочные знаки и т. Д.). См. Далее stackoverflow.com/questions/10067266/…
tripleee
Кроме того, поведение для несуществующих путей ошибочно; но я думаю, это то, что, возможно, пытается сказать фраза «но помни». Хотя поведение в Ubuntu определенно не в том, чтобы печатать текущий каталог, когда вы запрашиваете realpathнесуществующий каталог.
tripleee
Для единообразия, вероятно, предпочтительнее dir=$(dirname "$1"); file=$(basename "$1")использовать давно устаревший синтаксис обратной кавычки. Также обратите внимание на правильное цитирование аргументов.
tripleee
Ваш обновленный ответ, похоже, не исправляет многие ошибки и добавляет новые.
tripleee
пожалуйста, назовите мне несколько конкретных неудачных случаев, потому что все тесты, которые я выполняю на рабочем столе ubuntu 18.04, в порядке, спасибо.
occia