Как вы нормализуете путь к файлу в Bash?

204

Я хочу преобразовать /foo/bar/..в/foo

Есть команда bash, которая делает это?


Изменить: в моем практическом случае, каталог существует.

Фабьен
источник
4
Имеет ли значение, существует ли он вообще /foo/barили /fooвообще существует, или вас интересует только аспект манипуляции со строками в соответствии с правилами имен путей?
Чен Леви
5
@ twalberg ... это как-то придумано ...
Камило Мартин
2
@CamiloMartin не надуманный вообще - это именно то , что задает вопрос - прообразы /foo/bar/..к /foo, и используя bashкоманду. Если есть другие требования, которые не заявлены, то, возможно, они должны быть ...
Twalberg
14
@twalberg Ты слишком много делаешь TDD -_- '
Камило Мартин

Ответы:

194

если вы хотите скомпоновать часть имени файла из пути, «dirname» и «basename» - ваши друзья, и «realpath» также удобен.

dirname /foo/bar/baz 
# /foo/bar 
basename /foo/bar/baz
# baz
dirname $( dirname  /foo/bar/baz  ) 
# /foo 
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp

realpath альтернативы

Если realpathваша оболочка не поддерживается, вы можете попробовать

readlink -f /path/here/.. 

Также

readlink -m /path/there/../../ 

Работает так же, как

realpath -s /path/here/../../

в том, что путь не должен существовать, чтобы быть нормализованным.

Кент Фредрик
источник
5
Для тех из вас, кому нужно решение OS X, ознакомьтесь с ответом Адама Лисса ниже.
Трентон
stackoverflow.com/a/17744637/999943 Это сильно связанный ответ! Я наткнулся на обе эти записи о качестве за один день и хотел связать их вместе.
phyatt
realpath, похоже, был добавлен в coreutils в 2012 году. Смотрите историю файлов по адресу github.com/coreutils/coreutils/commits/master/src/realpath.c .
Ной Лавин
1
И то, realpathи другое readlinkпроисходит от основных утилит GNU, так что, скорее всего, вы получите либо оба, либо ни одного. Если я правильно помню, версия readlink на Mac немного отличается от версии GNU: - \
Antoine 'hashar' Musso
101

Я не знаю, есть ли прямая команда bash, чтобы сделать это, но я обычно делаю

normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"

и это работает хорошо.

Тим Уиткомб
источник
9
Это нормализует, но не разрешает мягкие ссылки. Это может быть ошибка или особенность. :-)
Адам Лисс
4
Это также имеет проблему, если определен $ CDPATH; потому что «cd foo» переключится на любой каталог «foo», который является подкаталогом $ CDPATH, а не просто «foo», который находится в текущем каталоге. Я думаю, что вам нужно сделать что-то вроде: CDPATH = "" cd "$ {dirToNormalize}" && pwd -P.
MJS
7
Ответ Тима определенно самый простой и самый портативный. С CDPATH легко работать: dir = "$ (unset CDPATH && cd" $ dir "&& pwd)"
Дэвид Блевинс,
2
Это может быть очень опасно ( rm -rf $normalDir), если dirToNormalize не существует!
Фрэнк Мейленаар
4
Да, вероятно, лучше всего использовать &&комментарий @ DavidBlevins.
Элиас Дорнелес
54

Попробуй realpath. Ниже приведен источник в полном объеме, настоящим пожертвовано в общественное достояние.

// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>   
#include <limits.h>

static char *s_pMyName;
void usage(void);

int main(int argc, char *argv[])
{
    char
        sPath[PATH_MAX];


    s_pMyName = strdup(basename(argv[0]));

    if (argc < 2)
        usage();

    printf("%s\n", realpath(argv[1], sPath));
    return 0;
}    

void usage(void)
{
    fprintf(stderr, "usage: %s PATH\n", s_pMyName);
    exit(1);
}
Адам Лисс
источник
1
Это входит в стандартную установку bash? Я получаю «команда не найдена» в нашей системе (bash 3.00.15 (1) на RHEL)
Тим Уиткомб
4
gnu.org/software/coreutils для readlink, но realpath поставляется из этого пакета: packages.debian.org/unstable/utils/realpath
Кент Фредрик,
8
Это стандартно для Ubuntu / BSD, а не Centos / OSX
Эрик Аронести
4
Вы должны по-настоящему ударить по части с помощью «Бонуса: это команда bash, доступная везде!» часть о realpath. Это также, случается, мой любимый, но вместо того, чтобы собрать ваш инструмент из источника. Это все в супертяжелом весе, и большую часть времени вы просто хотите readlink -f... Кстати, readlinkэто тоже НЕ встроенная bash, а часть coreutilsUbuntu.
Томаш Гандор
4
Вы сказали, что жертвуете это на всеобщее достояние, но в заголовке также написано «для любого некоммерческого использования» - вы действительно хотите пожертвовать это на всеобщее достояние? Это означало бы, что его можно использовать в коммерческих некоммерческих целях, а также в коммерческих целях ...
me_and
36

Портативное и надежное решение - использовать python, который установлен практически везде (включая Darwin). У вас есть два варианта:

  1. abspath возвращает абсолютный путь, но не разрешает символические ссылки:

    python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

  2. realpath возвращает абсолютный путь и при этом разрешает символические ссылки, генерируя канонический путь:

    python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file

В каждом случае path/to/fileможет быть как относительный, так и абсолютный путь.

loevborg
источник
7
Спасибо, это было единственное, что сработало. readlink или realpath недоступны в OS X. Python должен быть на большинстве платформ.
сорин
1
Просто чтобы уточнить, readlinkдоступно на OS X, только не с -fопцией. Портативные обходные пути обсуждаются здесь .
StvnW
3
Я буквально поражаюсь, что это единственное разумное решение, если вы не хотите переходить по ссылкам. Unix, кстати, моя нога.
DannoHung
34

Используйте утилиту readlink из пакета coreutils.

MY_PATH=$(readlink -f "$0")
mattalxndr
источник
1
У BSD нет флага -f, что означает, что это не удастся даже на последней MacOS Mojave и многих других системах. Забудьте об использовании -f, если вам нужна переносимость, это влияет на множество ОС.
Сорин
1
@sorin. Вопрос не в Mac, а в Linux.
mattalxndr
14

readlinkстандарт bash для получения абсолютного пути Он также имеет преимущество, заключающееся в возврате пустых строк, если пути или пути не существует (учитывая флаги для этого).

Чтобы получить абсолютный путь к каталогу, который может существовать или не существовать, но чьи родители существуют, используйте:

abspath=$(readlink -f $path)

Чтобы получить абсолютный путь к каталогу, который должен существовать вместе со всеми родителями:

abspath=$(readlink -e $path)

Чтобы канонизировать данный путь и следовать символическим ссылкам, если они существуют, но в противном случае игнорировать отсутствующие каталоги и просто возвращать путь в любом случае, это:

abspath=$(readlink -m $path)

Единственным недостатком является то, что readlink будет следовать ссылкам. Если вы не хотите переходить по ссылкам, вы можете использовать это альтернативное соглашение:

abspath=$(cd ${path%/*} && echo $PWD/${path##*/})

Это приведет к переходу в директорию $ path и распечатает текущий каталог вместе с файловой частью $ path. Если это не удалось chdir, вы получите пустую строку и ошибку на stderr.

Craig
источник
7
readlinkхороший вариант, если он доступен. Версия OS X не поддерживает опции -eили -f. В первых трех примерах вы должны иметь двойные кавычки $pathдля обработки пробелов или подстановочных знаков в имени файла. +1 для расширения параметра, но это имеет уязвимость безопасности. Если pathпусто, это будет cdв вашем домашнем каталоге. Вам нужны двойные кавычки. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
toxalot
Это был просто пример. Если вы одержимы безопасностью, то вам вообще не следует использовать bash или любой другой вариант оболочки. Кроме того, у bash есть свои проблемы, когда речь идет о кроссплатформенной совместимости, а также проблемы с изменением функциональности между основными версиями. OSX - это всего лишь одна из многих платформ с проблемами, относящимися к сценариям оболочки, не говоря уже о том, что она основана на BSD. Когда вам нужно быть действительно мультиплатформенным, вы должны быть POSIX-совместимым, поэтому расширение параметров действительно выходит за рамки. Посмотрите на Solaris или HP-UX некоторое время.
Крейг
6
Здесь нет никакого смысла в оскорблении, но важно указать на такие неясные вопросы, как это. Я просто хотел бы получить быстрый ответ на эту тривиальную проблему, и я бы доверял этому коду любой / все ввод, если бы не комментарий выше. Также важно поддерживать OS-X в этих обсуждениях bash. Есть много команд, которые, к сожалению, не поддерживаются в OS-X, и многие форумы принимают это как должное при обсуждении Bash, что означает, что мы будем продолжать сталкиваться с множеством кросс-платформенных проблем, если только они не будут решены раньше, чем позже.
Ребс
13

Старый вопрос, но есть гораздо более простой способ, если вы имеете дело с полными путями на уровне оболочки:

   abspath = "$ (cd" $ path "&& pwd)"

Поскольку cd происходит в подоболочке, он не влияет на основной скрипт.

Два варианта, предполагающие, что встроенные команды вашей оболочки принимают -L и -P:

   abspath = "$ (cd -P" $ path "&& pwd -P)" # физический путь с разрешенными символическими ссылками
   abspath = "$ (cd -L" $ path "&& pwd -L)" # логический путь, сохраняющий символические ссылки

Лично мне этот поздний подход редко нужен, если я по какой-то причине не увлечен символическими ссылками.

К вашему сведению: вариант получения начальной директории скрипта, который работает, даже если скрипт позже меняет свою текущую директорию.

name0 = "$ (базовое имя" $ 0 ")"; # базовое имя скрипта
dir0 = "$ (cd" $ (dirname "$ 0") "&& pwd)"; #absolute начальный каталог

Использование компакт-диска гарантирует, что у вас всегда есть абсолютный каталог, даже если скрипт запускается такими командами, как ./script.sh, который, без cd / pwd, часто дает просто ... Бесполезно, если сценарий делает CD позже.

Гилберт
источник
8

Как отметил Адам Лисс, realpathон не входит в комплект поставки. Который является позором, потому что это лучшее решение. Предоставленный исходный код великолепен, и я, вероятно, начну использовать его сейчас. Вот то, что я использовал до сих пор, и я делюсь здесь только для полноты:

get_abs_path() {
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
} 

Если вы хотите разрешить символические ссылки, просто замените pwdна pwd -P.

Джит
источник
Один вопрос с pwd -Pопцией для этого случая ... Подумайте, что произойдет, если бы $(basename "$1")была символическая ссылка на файл в другом каталоге. pwd -PТолько разрешает символические ссылки в части каталога пути, но не часть BASENAME.
Токсалот
7

Мое недавнее решение было:

pushd foo/bar/..
dir=`pwd`
popd

На основании ответа Тима Уиткомба.

schmunk
источник
Я подозреваю, что это не удастся, если аргумент не является каталогом. Предположим, я хочу знать, куда ведет / usr / bin / java?
Эдвард Фальк
1
Если вы знаете, что это файл, вы можете pushd $(dirname /usr/bin/java)попробовать.
schmunk
5

Не совсем ответ, но, возможно, дополнительный вопрос (первоначальный вопрос не был явным):

readlinkхорошо, если вы действительно хотите следовать символическим ссылкам. Но есть также вариант использования для простой нормализации ./и ../и //последовательности, которые могут быть сделаны чисто синтаксически, без канонизации символических ссылок. readlinkне хорошо для этого, и ни один realpath.

for f in $paths; do (cd $f; pwd); done

работает для существующих путей, но ломается для других.

sedСценарий , казалось бы хорошим выбором, за исключением того, что вы не можете итеративно заменить последовательности ( /foo/bar/baz/../..-> /foo/bar/..-> /foo) , не используя что - то вроде Perl, который не является безопасным предположить на всех системах, или с помощью какой - то уродливый цикл , чтобы сравнить выход sedк его вклад.

FWIW, однострочник с использованием Java (JDK 6+):

jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths
Джесси Глик
источник
realpathимеет -sвозможность не разрешить символические ссылки и только разрешать ссылки /./, /../и удалить лишние /символы. В сочетании с этой -mопцией realpathработает только с именем файла и не касается какого-либо фактического файла. Это звучит как идеальное решение. Но, увы, realpathдо сих пор отсутствует во многих системах.
toxalot
Удаление ..компонентов не может быть сделано синтаксически, когда задействованы символические ссылки. /one/two/../threeэто не то же самое , как /one/threeесли бы twoэто символическая ссылка на /foo/bar.
jrw32982 поддерживает Монику
@ jrw32982 да, как я уже сказал в своем ответе, это для случая использования, когда канонизация символической ссылки не нужна или не нужна.
Джесси Глик
@JesseGlick это не просто вопрос, хотите ли вы канонизировать символические ссылки. Ваш алгоритм на самом деле дает неправильный ответ. Чтобы ваш ответ был правильным, вы должны были бы априори знать, что не было никаких символических ссылок (или что они были только определенной формы). Ваш ответ говорит о том, что вы не хотите их канонизировать, а не о том, что на пути нет символических ссылок.
jrw32982 поддерживает Монику
В некоторых случаях нормализация должна выполняться без использования какой-либо фиксированной существующей структуры каталогов. Нормализация URI аналогична. В этих случаях присуще ограничение, заключающееся в том, что результат, как правило, будет неправильным, если рядом с каталогом, к которому применяется результат, окажутся символические ссылки.
Джесси Глик
5

Я опаздываю на вечеринку, но это решение, которое я разработал после прочтения нескольких тем вроде этого:

resolve_dir() {
        (builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}

Это разрешит абсолютный путь в $ 1, будет хорошо играть с ~, сохранит символические ссылки там, где они есть, и это не помешает вашему стеку каталогов. Возвращает полный путь или ничего, если он не существует. Он ожидает, что $ 1 будет каталогом и, возможно, потерпит неудачу, если это не так, но это легко проверить самостоятельно.

apottere
источник
4

Разговорчивый и немного поздний ответ. Мне нужно написать один, так как я застрял на старшей RHEL4 / 5. Я обрабатываю абсолютные и относительные ссылки и упрощаю записи //, /./ и somedir /../.

test -x /usr/bin/readlink || readlink () {
        echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
    }


test -x /usr/bin/realpath || realpath () {
    local PATH=/bin:/usr/bin
    local inputpath=$1
    local changemade=1
    while [ $changemade -ne 0 ]
    do
        changemade=0
        local realpath=""
        local token=
        for token in ${inputpath//\// }
        do 
            case $token in
            ""|".") # noop
                ;;
            "..") # up one directory
                changemade=1
                realpath=$(dirname $realpath)
                ;;
            *)
                if [ -h $realpath/$token ] 
                then
                    changemade=1
                    target=`readlink $realpath/$token`
                    if [ "${target:0:1}" = '/' ]
                    then
                        realpath=$target
                    else
                        realpath="$realpath/$target"
                    fi
                else
                    realpath="$realpath/$token"
                fi
                ;;
            esac
        done
        inputpath=$realpath
    done
    echo $realpath
}

mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`
alhernau
источник
3

Попробуйте наш новый продукт библиотеки Bash realpath-lib, который мы поместили на GitHub, для бесплатного и свободного использования. Он тщательно документирован и является отличным инструментом обучения.

Он разрешает локальные, относительные и абсолютные пути и не имеет никаких зависимостей, кроме Bash 4+; так что должно работать где угодно. Это бесплатно, чисто, просто и поучительно.

Ты можешь сделать:

get_realpath <absolute|relative|symlink|local file path>

Эта функция является ядром библиотеки:

function get_realpath() {

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

Он также содержит функции для get_dirname, get_filename, get_ stemname и validate_path. Попробуйте это на разных платформах и помогите улучшить его.

AsymLabs
источник
2

Проблема в realpathтом, что он недоступен в BSD (или OSX в этом отношении). Вот простой рецепт, извлеченный из довольно старой (2009 г.) статьи из Linux Journal , которая довольно переносима:

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

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}

Обратите внимание, что этот вариант также не требует пути для существования.

Андре Анхос
источник
Однако это не разрешает символические ссылки. Realpath обрабатывает пути, начинающиеся с корня, и по ходу идет по символическим ссылкам. Все это делает, свернуть родительские ссылки.
Мартин Питерс
2

Основываясь на ответе @ Andre, у меня могла бы быть немного лучшая версия, если кто-то ищет решение без петель, полностью основанное на обработке строк. Это также полезно для тех, кто не хочет разыменовывать любые символические ссылки, что является недостатком использования realpathили readlink -f.

Работает на bash версий 3.2.25 и выше.

shopt -s extglob

normalise_path() {
    local path="$1"
    # get rid of /../ example: /one/../two to /two
    path="${path//\/*([!\/])\/\.\./}"
    # get rid of /./ and //* example: /one/.///two to /one/two
    path="${path//@(\/\.\/|\/+(\/))//}"
    # remove the last '/.'
    echo "${path%%/.}"
}

$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config
ΣοδεMεδις
источник
Это хорошая идея, но я потратил 20 минут, пытаясь заставить это работать на разных версиях bash. Оказывается, чтобы это работало, нужно включить параметр оболочки extglob, а по умолчанию это не так. Когда речь заходит о функциональности bash, важно указать как необходимую версию, так и параметры, отличные от заданных по умолчанию, поскольку эти данные могут различаться в разных ОС. Например, последняя версия Mac OSX (Yosemite) поставляется только с устаревшей версией bash (3.2).
drwatsoncode
Извините @ricovox; Я сейчас обновил их. Мне не терпится узнать точную версию Bash, которая у вас есть. Вышеприведенная формула (обновленная) работает на CentOS 5.8, которая поставляется с bash 3.2.25
ϲοδεMεδιϲ
Извините за путаницу. Этот код работал на моей версии Mac OSX bash (3.2.57), как только я включил extglob. Моя заметка о версиях bash была более общей (на самом деле это относится и к другому ответу, касающемуся регулярных выражений в bash).
drwatsoncode 13.10.15
2
Я ценю ваш ответ, хотя. Я использовал его в качестве основы для себя. Между прочим, я заметил несколько случаев, когда ваш сбой: (1) Относительные пути hello/../world(2) Точки в имени файла /hello/..world(3) Точки после двойной косой черты /hello//../world(4) Точка до или после двойной косой черты /hello//./worldили /hello/.//world (5) Родитель после текущей: /hello/./../world/ (6) Родитель after Parent: /hello/../../worldи т. д. - некоторые из них можно исправить, используя цикл для исправления, пока путь не перестанет меняться. (Также удалите dir/../, /dir/..но не удалите dir/..с конца.)
drwatsoncode
0

Основываясь на превосходном фрагменте Python от Loveborg, я написал это:

#!/bin/sh

# Version of readlink that follows links to the end; good for Mac OS X

for file in "$@"; do
  while [ -h "$file" ]; do
    l=`readlink $file`
    case "$l" in
      /*) file="$l";;
      *) file=`dirname "$file"`/"$l"
    esac
  done
  #echo $file
  python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done
Эдвард Фальк
источник
0
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

Это работает, даже если файл не существует. Для этого требуется каталог, содержащий файл.

user240515
источник
GNU Realpath также не требует существования последнего элемента в пути, если только вы не используетеrealpath -e
Martijn Pieters
0

Мне нужно решение, которое сделало бы все три:

  • Работа на стоковом Mac. realpathи readlink -fаддоны
  • Разрешить символические ссылки
  • Обработка ошибок

Ни в одном из ответов не было ни № 1, ни № 2. Я добавил # 3, чтобы спасти других от дальнейшего бритья.

#!/bin/bash

P="${1?Specify a file path}"

[ -e "$P" ] || { echo "File does not exist: $P"; exit 1; }

while [ -h "$P" ] ; do
    ls="$(ls -ld "$P")"
    link="$(expr "$ls" : '.*-> \(.*\)$')"
    expr "$link" : '/.*' > /dev/null &&
        P="$link" ||
        P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"

Вот короткий тестовый пример с некоторыми искривленными пробелами в путях, чтобы полностью использовать цитату

mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "

cd  "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "
Дэвид Блевинс
источник
0

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

#! /bin/sh                                                                                                                                                

function normalize {
  local rc=0
  local ret

  if [ $# -gt 0 ] ; then
    # invalid
    if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
      echo $1
      return -1
    fi

    # convert to absolute path
    if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
      normalize "`pwd`/$1"
      return $?
    fi

    ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
  else
    read line
    normalize "$line"
    return $?
  fi

  if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
    ret=`normalize "$ret"`
    rc=$?
  fi

  echo "$ret"
  return $rc
}

https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c

bestOfSong
источник
0

Я сделал встроенную функцию только для обработки этого с акцентом на максимально возможную производительность (для удовольствия). Он не разрешает символические ссылки, поэтому он в основном такой же, как realpath -sm.

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () { 
  ${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "${s:-/}";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;
}

Примечание. Эта функция не обрабатывает пути, начинающиеся с //, поскольку ровно две двойные косые черты в начале пути являются поведением, определяемым реализацией. Тем не менее, он справляется /, ///и так далее, просто отлично.

Эта функция, кажется, правильно обрабатывает все крайние случаи, но могут быть некоторые из них, с которыми я не имел дело.

Замечание по производительности: при вызове с тысячами аргументов abspathработает примерно в 10 раз медленнее, чем realpath -sm; при вызове с одним аргументом abspathработает> в 110 раз быстрее, чем realpath -smна моей машине, в основном из-за того, что нет необходимости каждый раз выполнять новую программу.

Джеффри Кэш
источник
-1

Сегодня я обнаружил, что вы можете использовать statкоманду для определения путей.

Так что для директории типа "~ / Documents":

Вы можете запустить это:

stat -f %N ~/Documents

Чтобы получить полный путь:

/Users/me/Documents

Для символических ссылок вы можете использовать формат% Y:

stat -f %Y example_symlink

Который может вернуть результат как:

/usr/local/sbin/example_symlink

Параметры форматирования могут отличаться в других версиях * NIX, но они работают для меня в OSX.

coldlogic
источник
1
stat -f %N ~/Documentsлиния красная селедка ... ваша оболочка меняет ~/Documentsс /Users/me/Documents, а statпросто печатать его аргумент дословно.
danwyand
-4

Простое решение с использованием node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
Artisan72
источник