Как нормализовать путь к файлу в Bash?
Я хочу, чтобы преобразовать /foo/bar/..
до /foo
есть ли команда bash, которая делает это?
Edit: в моем практическом случае каталог существует.
21 ответов
если вы хотите проглотить часть имени файла из пути, "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/../../
в том, что путь не должен существовать, чтобы быть нормализован.
Я не знаю, есть ли команды bash для этого, но я обычно делаю
normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"
и он работает хорошо.
попробовать 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);
}
портативным и надежным решением является использование python, который предустановлен практически везде (включая Darwin). У вас есть два варианта:
-
abspath
возвращает абсолютный путь, но не разрешает символические ссылки:python -c "import os,sys; print os.path.abspath(sys.argv[1])" path/to/file
-
realpath
возвращает абсолютный путь и при этом разрешает символические ссылки, генерируя канонический путь:python -c "import os,sys; print os.path.realpath(sys.argv[1])" path/to/file
в каждом случае, path/to/file
может быть либо относительный, либо абсолютный путь.
readlink
является стандартом bash для получения абсолютного пути. Он также имеет преимущество возвращать пустые строки, Если пути или путь не существуют (учитывая флаги для этого).
чтобы получить абсолютный путь к каталогу, который может или не может существовать, но кто родители существуют, использовать:
abspath=$(readlink -f $path)
чтобы получить абсолютный путь к каталогу, который должен существовать вместе со всеми родителями:
abspath=$(readlink -e $path)
в canonicalise данный путь и следовать по символическим ссылкам, если они случается существовать, но в противном случае игнорируйте отсутствующие каталоги и просто возвращайте путь в любом случае, это:
abspath=$(readlink -m $path)
единственный недостаток заключается в том, что более ранних версий будут переходить по ссылкам. Если вы не хотите следовать ссылкам, вы можете использовать это альтернативное соглашение:
abspath=$(cd ${path%/*} && echo $PWD/${path##*/})
это будет chdir в часть каталога $path и распечатать текущий каталог вместе с частью файла $path. Если это не удается chdir, вы получаете пустую строку и ошибку на stderr.
старый вопрос, но есть более простой способ если вы имеете дело с полными именами пути на уровне оболочки:
abspath="$( cd "$path" && pwd )"
поскольку компакт-диск происходит в подсетке, он не влияет на основной скрипт.
два варианта, Предположим, ваша оболочка встроенные команды accept-L и-P, являются:
abspath="$( cd -P "$path" && pwd -P )" #physical path with resolved symlinks abspath="$( cd -L "$path" && pwd -L )" #logical path preserving symlinks
лично мне редко нужен этот более поздний подход, если я не очарован символическими ссылками по какой-то причине.
FYI: изменение на получать начальный каталог скрипта, который работает, даже если скрипт изменяет текущий каталог позже.
name0="$(basename "")"; #base name of script dir0="$( cd "$( dirname "" )" && pwd )"; #absolute starting dir
использование CD гарантирует, что у вас всегда есть абсолютный каталог, даже если скрипт запускается такими командами, как ./script.sh который, без cd /pwd, часто дает просто.. Бесполезно, если скрипт сделает cd позже.
как отметил Адам Лисс realpath
не в комплекте с каждым дистрибутивом. Жаль, потому что это лучшее решение. Предоставленный исходный код отличный, и я, вероятно, начну использовать его сейчас. Вот то, что я использовал до сих пор, что я разделяю здесь только для полноты:
get_abs_path() {
local PARENT_DIR=$(dirname "")
cd "$PARENT_DIR"
local ABS_PATH="$(pwd)"/"$(basename "")"
cd - >/dev/null
echo "$ABS_PATH"
}
если вы хотите, чтобы он разрешал символические ссылки, просто замените pwd
С pwd -P
.
разговорчивый, и немного поздний ответ. Мне нужно написать один, так как я застрял на старом RHEL4/5. I обрабатывает абсолютные и относительные ссылки и упрощает //,/./ и somedir/../ вступления.
test -x /usr/bin/readlink || readlink () {
echo $(/bin/ls -l | /bin/cut -d'>' -f 2)
}
test -x /usr/bin/realpath || realpath () {
local PATH=/bin:/usr/bin
local inputpath=
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`
не совсем ответ, но, возможно, последующий вопрос (первоначальный вопрос не был явным):
readlink
отлично, если вы действительно хотите следовать символическим ссылкам. Но есть также вариант использования для простой нормализации ./
и ../
и //
последовательности, которые могут быть выполнены чисто синтаксически,без канонизация символических ссылок. readlink
не годится для этого, и ни realpath
.
for f in $paths; do (cd $f; pwd); done
работает для существующих путей, но и перерывы для другие.
A 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
Я опаздываю на вечеринку, но это решение, которое я создал после чтения кучки таких потоков:
resolve_dir() {
(builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}
это разрешит абсолютный путь $1, сыграет хорошо с~, сохранит символические ссылки в пути, где они есть, и он не будет связываться с вашим стеком каталогов. Он возвращает полный путь или ничего, если он не существует. Он ожидает, что $1 будет каталогом и, вероятно, потерпит неудачу, если это не так, но это легко проверить самостоятельно.
попробуйте наш новый продукт библиотеки Bash realpath-lib что мы разместили на GitHub для свободного и свободного использования. Это тщательно документировано и является отличным инструментом обучения.
Он разрешает локальные, относительные и абсолютные пути и не имеет никаких зависимостей, кроме Bash 4+; поэтому он должен работать практически в любом месте. Это бесплатно, чисто, просто и поучительно.
Вы можете сделать:
get_realpath <absolute|relative|symlink|local file path>
эта функция является ядром библиотека:
function get_realpath() {
if [[ -f "" ]]
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. Попробуйте его на разных платформах и помогите его улучшить.
основываясь на ответе @Andre, у меня может быть немного лучшая версия, если кто-то после решения без цикла, полностью основанного на манипуляции строками. Это также полезно для тех, кто не хочет, чтобы разыменовать какие-либо ссылки, что является недостатком использования realpath
или readlink -f
.
он работает на версиях bash 3.2.25 и выше.
shopt -s extglob
normalise_path() {
local path=""
# 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
проблема с 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
}
обратите внимание, что этот вариант также делает не требуется, чтобы путь существовал.
основываясь на отличном фрагменте 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
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)
это работает, даже если файл не существует. Для этого требуется каталог, содержащий файл.
мне нужно было решение, которое сделало бы все три:
- работа на складе 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 "
Я знаю, что это древний вопрос. Я все еще предлагаю альтернативу. Недавно я столкнулся с той же проблемой и не нашел существующей и переносимой команды для этого. Поэтому я написал следующий скрипт, который содержит функцию, которая может сделать трюк.
#! /bin/sh
function normalize {
local rc=0
local ret
if [ $# -gt 0 ] ; then
# invalid
if [ "x`echo | grep -E '^/\.\.'`" != "x" ] ; then
echo
return -1
fi
# convert to absolute path
if [ "x`echo | grep -E '^\/'`" == "x" ] ; then
normalize "`pwd`/"
return $?
fi
ret=`echo | 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
я обнаружил сегодня, что вы можете использовать stat
команда для разрешения путей.
Итак, для каталога типа "~ / Documents":
вы можете выполнить следующее:
stat -f %N ~/Documents
чтобы получить полный путь:
/Users/me/Documents
для символических ссылок можно использовать параметр % y format:
stat -f %Y example_symlink
который может вернуть результат, например:
/usr/local/sbin/example_symlink
параметры форматирования могут отличаться на другие версии *NIX, но они работали для меня на OSX.
простое решение с помощью node.js
:
#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));