Команда 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
имеет возможность получить абсолютный путь, не удосуживаясь разрешить символические ссылки (наряду с несколькими другими опциями, чтобы получить различные типы путей, абсолютные или относительные к некоторому каталогу).
Это требует нескольких замечаний:
- Символьные ссылки могут быть разрешены, только если все, на что они должны ссылаться, уже создано, что, очевидно, не всегда так. Команды
realpath
а такжеreadlink
есть варианты для учета этого. - каталог на пути позже может стать символической ссылкой, что означает, что путь больше не является
canonical
, Следовательно, концепция зависит от времени (или окружающей среды). - даже в идеальном случае, когда все символические ссылки могут быть разрешены, может существовать более одного
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"
- Затем мы получаем часть dirname этого пути (вы можете передать либо dir, либо файл в этот скрипт):
dirname "$1"
- Тогда мы
cd "$(dirname "$1")
в этот относительный каталог и получить абсолютный путь для него, запустивpwd
команда оболочки - После этого мы добавляем basename к абсолютному пути:
$(basename "$1")
- В качестве последнего шага мы
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
команда. Он отображает абсолютный путь к предоставленному файлу, но только если он существует.