Команда bash/fish для вывода абсолютного пути к файлу

Вопрос: существует ли простая команда sh/bash/zsh/fish/... для печати абсолютного пути к файлу, который я передаю?

Случай использования: я в каталоге /a/b и я хотел бы напечатать полный путь к файлу c в командной строке, чтобы я мог легко вставить его в другую программу: /a/b/c, Простая, но небольшая программа для этого, вероятно, может сэкономить мне около 5 секунд, когда дело доходит до обработки длинных путей, что в итоге складывается. Поэтому меня удивляет, что я не могу найти стандартную утилиту для этого - неужели ее нет?

Вот пример реализации, abspath.py:

#!/usr/bin/python
# Author: Diggory Hardy <diggory.hardy@gmail.com>
# Licence: public domain
# Purpose: print the absolute path of all input paths

import sys
import os.path
if len(sys.argv)>1:
    for i in range(1,len(sys.argv)):
        print os.path.abspath( sys.argv[i] )
    sys.exit(0)
else:
    print >> sys.stderr, "Usage: ",sys.argv[0]," PATH."
    sys.exit(1)

23 ответа

Решение

Пытаться realpath,

$ realpath example.txt
/home/username/example.txt

Пытаться readlink который разрешит символические ссылки:

readlink -e /foo/bar/baz
#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"

Забудь о readlink а также realpath который может или не может быть установлен в вашей системе.

Более подробно об ответе клана выше здесь выражается как функция:

#!/bin/bash
get_abs_filename() {
  # $1 : relative filename
  echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
}

затем вы можете использовать его так:

myabsfile=$(get_abs_filename "../../foo/bar/file.txt")

Как и почему это работает?

Решение использует тот факт, что Bash встроен pwd Команда выведет абсолютный путь к текущему каталогу при вызове без аргументов.

Почему мне нравится это решение?

Это портативный и не требует ни readlink или же realpath которого часто не существует при установке по умолчанию данного дистрибутива Linux/Unix.

Что если dir не существует?

Как указано выше, функция потерпит неудачу и напечатает на stderr, если указанный путь к каталогу не существует. Это может быть не то, что вы хотите. Вы можете расширить функцию для обработки этой ситуации:

#!/bin/bash
get_abs_filename() {
  # $1 : relative filename
  if [ -d "$(dirname "$1")" ]; then
    echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
  fi
}

Теперь он вернет пустую строку, если один из родительских каталогов не существует.

Как вы справляетесь с трейлингом '..' или '.' на входе?

Ну, в этом случае он дает абсолютный путь, но не минимальный. Это будет выглядеть так:

/Users/bob/Documents/..

Если вы хотите разрешить "..", вам нужно сделать скрипт следующим образом:

get_abs_filename() {
  # $1 : relative filename
  filename=$1
  parentdir=$(dirname "${filename}")

  if [ -d "${filename}" ]; then
      echo "$(cd "${filename}" && pwd)"
  elif [ -d "${parentdir}" ]; then
    echo "$(cd "${parentdir}" && pwd)/$(basename "${filename}")"
  fi
}
$ readlink -m FILE
/path/to/FILE

Это лучше чем readlink -e FILE или же realpathпотому что он работает, даже если файл не существует.

Этот относительный путь к функции оболочки конвертера абсолютного пути

  • не требует никаких утилит (просто cd а также pwd)
  • работает для каталогов и файлов
  • ручки .. а также .
  • обрабатывает пробелы в dir или именах файлов
  • требует, чтобы файл или каталог существовал
  • ничего не возвращает, если по данному пути ничего не существует
  • обрабатывает абсолютные пути как ввод (пропускает их по существу)

Код:

function abspath() {
    # generate absolute path from relative path
    # $1     : relative filename
    # return : absolute path
    if [ -d "$1" ]; then
        # dir
        (cd "$1"; pwd)
    elif [ -f "$1" ]; then
        # file
        if [[ $1 = /* ]]; then
            echo "$1"
        elif [[ $1 == */* ]]; then
            echo "$(cd "${1%/*}"; pwd)/${1##*/}"
        else
            echo "$(pwd)/$1"
        fi
    fi
}

Образец:

# assume inside /parent/cur
abspath file.txt        => /parent/cur/file.txt
abspath .               => /parent/cur
abspath ..              => /parent
abspath ../dir/file.txt => /parent/dir/file.txt
abspath ../dir/../dir   => /parent/dir          # anything cd can handle
abspath doesnotexist    =>                      # empty result if file/dir does not exist
abspath /file.txt       => /file.txt            # handle absolute path input

Примечание: это основано на ответах от nolan6000 и bsingh, но исправляет регистр файлов.

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

find команда может помочь

find $PWD -name ex*
find $PWD -name example.log

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

find $PWD

Я использую это в Solaris 10, в котором нет других упомянутых утилит.

Вот функция zsh-only, которая мне нравится за ее компактность. Он использует модификатор расширения 'A' - см. Zshexpn(1).

realpath() { for f in "$@"; do echo ${f}(:A); done }

Если у вас нет утилит readlink или realpath, вы можете использовать следующую функцию, которая работает в bash и zsh (не уверен насчет остальных).

abspath () { case "$1" in /*)printf "%s\n" "$1";; *)printf "%s\n" "$PWD/$1";; esac; }

Это также работает для несуществующих файлов (как и функция Python os.path.abspath).

к несчастью abspath ./../somefile не избавляется от точек.

Обычно такой вещи как absolute path в файл (это утверждение означает, что может быть более одного в целом, следовательно, использование определенной статьи не подходит). absolute path любой путь, начинающийся с корня "/" и обозначающий файл без неоднозначности независимо от рабочего каталога (см., например, википедию).

relative path путь, который нужно интерпретировать, начиная с другого каталога. Это может быть рабочий каталог, если это relative path манипулировать приложением (хотя и не обязательно). Когда он находится в символической ссылке в каталоге, он, как правило, подразумевается относительно этого каталога (хотя пользователь может иметь в виду и другое использование).

Следовательно, абсолютный путь - это просто путь относительно корневого каталога.

Путь (абсолютный или относительный) может содержать или не содержать символические ссылки. Если это не так, это также несколько непроницаемо для изменений в структуре связи, но это не обязательно требуется или даже желательно. Некоторые люди называют canonical path (или же canonical file name или же resolved path) absolute path в котором все символические ссылки были разрешены, т. е. были заменены путем к тому, на что они ссылаются. Команды realpath а также readlink оба ищут канонический путь, но только realpath имеет возможность получить абсолютный путь, не удосуживаясь разрешить символические ссылки (наряду с несколькими другими опциями, чтобы получить различные типы путей, абсолютные или относительные к некоторому каталогу).

Это требует нескольких замечаний:

  1. Символьные ссылки могут быть разрешены, только если все, на что они должны ссылаться, уже создано, что, очевидно, не всегда так. Команды realpath а также readlink есть варианты для учета этого.
  2. каталог на пути позже может стать символической ссылкой, что означает, что путь больше не является canonical, Следовательно, концепция зависит от времени (или окружающей среды).
  3. даже в идеальном случае, когда все символические ссылки могут быть разрешены, может существовать более одного canonical path в файл, по двум причинам:
    • раздел, содержащий файл, возможно, был смонтирован одновременно (ro) в нескольких точках монтирования.
    • могут быть жесткие ссылки на файл, что означает, что файл существует в нескольких разных каталогах.

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

Это расширяет краткое обсуждение темы в ответе на другой похожий вопрос на Bash: получить абсолютный путь относительно

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

Самый простой, если вы хотите использовать только встроенные функции, вероятно:

find `pwd` -name fileName

Всего два дополнительных слова для ввода, и это будет работать во всех системах UNIX, а также в OSX.

dogbane ответьте с описанием, что происходит:

#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

Объяснение:

  1. Этот скрипт получает относительный путь в качестве аргумента "$1"
  2. Затем мы получаем часть dirname этого пути (вы можете передать либо dir, либо файл в этот скрипт): dirname "$1"
  3. Тогда мы cd "$(dirname "$1") в этот относительный каталог и получить абсолютный путь для него, запустив pwd команда оболочки
  4. После этого мы добавляем basename к абсолютному пути: $(basename "$1")
  5. В качестве последнего шага мы echo Это

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

      # node is in $PATH variable
type -P node
# /home/user/.asdf/shims/node
cd /tmp
touch node
readlink -e node
# /tmp/node
readlink -m node
# /tmp/node
readlink -f node
# /tmp/node
echo "$(cd "$(dirname "node")"; pwd -P)/$(basename "node")"
# /tmp/node
realpath node
# /tmp/node
realpath -e node
# /tmp/node

# Now let's say that for some reason node does not exist in current directory
rm node
readlink -e node
# <nothing printed>
readlink -m node    
# /tmp/node         # Note: /tmp/node does not exist, but is printed
readlink -f node
# /tmp/node         # Note: /tmp/node does not exist, but is printed
echo "$(cd "$(dirname "node")"; pwd -P)/$(basename "node")"
# /tmp/node         # Note: /tmp/node does not exist, but is printed
realpath node
# /tmp/node         # Note: /tmp/node does not exist, but is printed
realpath -e node
# realpath: node: No such file or directory

На основании вышеизложенного могу сделать вывод, что: realpath -eа также readlink -eможет использоваться для нахождения абсолютного пути к файлу, который, как мы ожидаем, существует в текущем каталоге, без влияния на результат переменной . Единственная разница в том, что realpathвыводит на стандартный вывод, но оба возвращают код ошибки, если файл не найден:

      cd /tmp
rm node
realpath -e node ; echo $?
# realpath: node: No such file or directory
# 1
readlink -e node ; echo $?
# 1

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

      type -P example.txt
# /path/to/example.txt

# Or if you want to follow links
readlink -e $(type -P example.txt)
# /originalpath/to/example.txt

# If the file you are looking for is an executable (and wrap again through `readlink -e` for following links )
which executablefile
# /opt/bin/executablefile

И а, запасной вариант $PATHесли отсутствует , пример:

      cd /tmp
touch node
echo $(readlink -e node || type -P node)
# /tmp/node
rm node
echo $(readlink -e node || type -P node)
# /home/user/.asdf/shims/node

Ответить с помощью Homebrew

realpath - лучший ответ, но если он у вас не установлен, вы должны сначала запустить brew install coreutilsкоторый установит coreutils с множеством потрясающих функций. Написание пользовательской функции и ее экспорт - это слишком много работы и риск ошибки для чего-то вроде этого, вот две строки:

$ brew install coreutils
$ realpath your-file-name.json 

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

#!/bin/bash
/usr/bin/find "$PWD" -maxdepth 1 -mindepth 1 -name "$1"

Я не уверен, почему, но в OS X при вызове сценария "$PWD" расширяется до абсолютного пути. Когда команда find вызывается из командной строки, это не так. Но он делает то, что я хочу... наслаждайся.

Для каталогов dirname споткнуться ../ и возвращается ./,

Функцию nolan6000 можно изменить, чтобы исправить это:

get_abs_filename() {
  # $1 : relative filename
  if [ -d "${1%/*}" ]; then
    echo "$(cd ${1%/*}; pwd)/${1##*/}"
  fi
}

В зш да, есть.

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

Пример:

      f='../example.txt'
echo ${f:P}

Вышеупомянутое напечатает абсолютный путь../example.txt.

Альтернативно вы можете использовать:Aмодификатор для дальнейшего разрешения символических ссылок, но это зависит отrealpathустанавливается.

      f='../example.txt'
echo ${f:A}

См. https://zsh.sourceforge.io/Doc/Release/Expansion.html#Modifiers .

Это не ответ на вопрос, а для тех, кто занимается сценариями:

echo `cd "$1" 2>/dev/null&&pwd||(cd "$(dirname "$1")";pwd|sed "s|/*\$|/${1##*/}|")`

он обрабатывает / .. ./ и т. д. правильно. У меня тоже вроде работает на OSX

Ответ Александра Климечека нормальный, если ваш сценарий может настаивать на наличии оболочки, совместимой с bash или bash. Он не будет работать с оболочкой, совместимой только с POSIX.

Также, когда конечный файл является файлом в корневом каталоге, вывод будет //file, что не является технически некорректным (двойной / рассматриваются системой как одиночные), но это выглядит странно.

Вот версия, которая работает с каждой оболочкой, совместимой с POSIX, все внешние инструменты, которые она использует, также требуются стандартом POSIX, и она явно обрабатывает случай корневого файла:

#!/bin/sh

abspath ( ) {
    if [ ! -e "$1" ]; then
        return 1
    fi

    file=""
    dir="$1"
    if [ ! -d "$dir" ]; then
        file=$(basename "$dir")
        dir=$(dirname "$dir")
    fi

    case "$dir" in
        /*) ;;
        *) dir="$(pwd)/$dir"
    esac
    result=$(cd "$dir" && pwd)

    if [ -n "$file" ]; then
        case "$result" in
            */) ;;
             *) result="$result/"
        esac
        result="$result$file"
    fi

    printf "%s\n" "$result"
}

abspath "$1"

Поместите это в файл и сделайте его исполняемым, и у вас будет инструмент CLI, чтобы быстро получить абсолютный путь к файлам и каталогам. Или просто скопируйте функцию и используйте ее в своих собственных сценариях, соответствующих POSIX. Он превращает относительные пути в абсолютные и возвращает абсолютные как есть.

Интересные модификации:

Если заменить строку result=$(cd "$dir" && pwd) с участием result=$(cd "$dir" && pwd -P), то все символические ссылки в пути к окончательному файлу также разрешаются.

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

abspath ( ) {
    if [ ! -e "$1" ]; then
        return 1
    fi

    case "$1" in
        /*)
            printf "%s\n" "$1"
            return 0
    esac

    file=""
    dir="$1"
    if [ ! -d "$dir" ]; then
        file=$(basename "$dir")
        dir=$(dirname "$dir")
    fi

    result=$(cd "$dir" && pwd)

    if [ -n "$file" ]; then
        case "$result" in
            */) ;;
            *) result="$result/"
        esac
        result="$result$file"
    fi

    printf "%s\n" "$result"
}

И поскольку возникнет вопрос: почему printf вместо echo?

echoпредназначен в первую очередь для вывода сообщений пользователя на стандартный вывод. Много echoповедение, на которое полагаются авторы сценариев, на самом деле не определено. Даже знаменитый -n стандартизирован или использование \tдля табл. Стандарт POSIX гласит:

Строка для вывода на стандартный вывод. Если первым операндом является -n или если какой-либо из операндов содержит символ, результаты определяются реализацией.
- https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html

Таким образом, всякий раз, когда вы хотите что-то записать в стандартный вывод, и это не предназначено для печати сообщения пользователю, рекомендуется использовать printf как поведение printfточно определено. Моя функция использует stdout для передачи результата, это не сообщение для пользователя и поэтому используется только printf гарантирует отличную портативность.

#! /bin/bash

file="$@"
realpath "$file" 2>/dev/null || eval realpath $(echo $file | sed 's/ /\\ /g')

Это восполняет недостатки realpath, сохраните его в сценарии оболочки fullpath, Теперь вы можете позвонить:

$ cd && touch a\ a && rm A 2>/dev/null 
$ fullpath "a a"
/home/user/a a
$ fullpath ~/a\ a
/home/user/a a
$ fullpath A
A: No such file or directory.

Альтернатива для получения абсолютного пути в Ruby:

realpath() {ruby -e "require 'Pathname'; puts Pathname.new('$1').realpath.to_s";}

Работает без аргументов (текущая папка) и относительного и абсолютного пути к файлу или папке в качестве аргумента.

Я использую одну линию

      (cd ${FILENAME%/*}; pwd)

Однако это можно использовать только тогда, когда $FILENAMEимеет ведущий путь любого вида (относительный или абсолютный), который действительно существует. Если ведущего пути вообще нет, то ответ прост: $PWD. Если ведущего пути не существует, то ответ может быть неопределенным, в противном случае ответ просто ${FILENAME%/*}если путь абсолютный.

Собрав все это вместе, я бы предложил использовать следующую функцию

      function abspath() {
  # argument 1: file pathname (relative or absolute)
  # returns: file pathname (absolute)
  if [ "$1" == "${1##*/}" ]; then # no path at all
    echo "$PWD"
  elif [ "${1:0:1}" == "/" -a "${1/../}" == "$1" ]; then # strictly absolute path
    echo "${1%/*}"
  else # relative path (may fail if a needed folder is non-existent)
    echo "$(cd ${1%/*}; pwd)"
  fi
}

Обратите также внимание, что это работает только в bashи совместимые оболочки. Я не верю, что замены работают в простой оболочке sh.

Привет, ребята, я знаю, что это старая ветка, но я просто отправляю это для ссылки любому, кто посетил это, как я. Если я правильно понял вопрос, думаю, locate $filename команда. Он отображает абсолютный путь к предоставленному файлу, но только если он существует.

Другие вопросы по тегам