Как вы нормализуете путь к файлу в Bash?
Я хочу преобразовать /foo/bar/..
в /foo
Есть ли команда bash, которая делает это?
Изменить: в моем практическом случае каталог существует.
Ответы
Ответ 1
если вы хотите chomp часть имени файла из пути, "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, похоже, не является стандартной проблемой.
Ближе всего вы можете ознакомиться со стандартом на складе:
readlink -f /path/here/..
Realpath, похоже, исходит от debian и не является частью coreutils:
http://packages.debian.org/unstable/utils/realpath
Который изначально был частью пакета DWWW.
(также доступен в gentoo как app-admin/realpath)
readlink -m /path/there/../../
Работает так же, как
realpath -s /path/here/../../
в том смысле, что для его нормализации не нужен путь к существованию.
Ответ 2
Я не знаю, есть ли команда bash для этого, но обычно я делаю
normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"
и он работает хорошо.
Ответ 3
Попробуйте 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);
}
Ответ 4
Используйте утилиту readlink из пакета coreutils.
MY_PATH=$(readlink -f "$0")
Ответ 5
Портативное и надежное решение - использовать python, который предварительно установлен практически везде (включая Дарвина). У вас есть два варианта:
-
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
может быть либо относительным, либо абсолютным путем.
Ответ 6
readlink
- это стандарт bash для получения абсолютного пути. Это также имеет преимущество возврата пустых строк, если пути или путь не существует (учитывая флаги, чтобы сделать это).
Чтобы получить абсолютный путь к каталогу, который может существовать или не существовать, но какие родители существуют, используйте:
abspath=$(readlink -f $path)
Чтобы получить абсолютный путь к каталогу, который должен существовать вместе со всеми родителями:
abspath=$(readlink -e $path)
Чтобы канонизировать данный путь и следовать символическим ссылкам, если они существуют, но в противном случае игнорировать недостающие каталоги и просто возвращать путь в любом случае, это:
abspath=$(readlink -m $path)
Единственным недостатком является то, что readlink будет следовать ссылкам. Если вы не хотите следовать ссылкам, вы можете использовать это альтернативное соглашение:
abspath=$(cd ${path%/*} && echo $PWD/${path##*/})
Это будет chdir для части каталога пути $path и распечатает текущий каталог вместе с файловой частью $path. Если ему не удается выполнить chdir, вы получите пустую строку и ошибку на stderr.
Ответ 7
Мое последнее решение было:
pushd foo/bar/..
dir=`pwd`
popd
Основываясь на ответе Тима Уиткомба.
Ответ 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
.
Ответ 9
Говорит, и немного поздно ответ. Мне нужно написать его, так как я застрял на старших 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`
Ответ 10
Не совсем ответ, но, возможно, следующий вопрос (оригинальный вопрос не был явным):
readlink
отлично, если вы действительно хотите следовать символическим ссылкам. Но есть и прецедент для просто нормализации последовательностей ./
и ../
и //
, которые могут выполняться чисто синтаксически, без канонизирующих символических ссылок. readlink
не подходит для этого, и не является realpath
.
for f in $paths; do (cd $f; pwd); done
работает для существующих путей, но прерывается для других.
A sed
script показалось бы хорошей ставкой, за исключением того, что вы не можете итеративно заменять последовательности (/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
Ответ 11
Я опаздываю на вечеринку, но это решение, которое я разработал после прочтения кучи таких как:
resolve_dir() {
(builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}
Это разрешит абсолютный путь в $1, поиграйте с символом ~, сохраните символические ссылки в пути, где они есть, и это не испортит ваш стек каталога. Он возвращает полный путь или ничего, если он не существует. Он ожидает, что $1 станет каталогом и, скорее всего, не сработает, если это не так, но это простая проверка, чтобы сделать сам.
Ответ 12
Старый вопрос, но есть гораздо более простой способ , если вы имеете дело с полными именами путей на уровне оболочки:
abspath="$( cd "$path" && pwd )"
Поскольку cd происходит в подоболочке, это не влияет на основной script.
Две вариации, предполагающие, что ваши встроенные команды оболочки принимают -L и -P,:
abspath="$( cd -P "$path" && pwd -P )" #physical path with resolved symlinks
abspath="$( cd -L "$path" && pwd -L )" #logical path preserving symlinks
Лично мне редко нужен этот более поздний подход, если я почему-то не увлекся символическими ссылками.
FYI: изменение при получении стартового каталога script, который работает, даже если script впоследствии изменит его текущий каталог.
name0="$(basename "$0")"; #base name of script
dir0="$( cd "$( dirname "$0" )" && pwd )"; #absolute starting dir
Использование компакт-диска гарантирует, что у вас всегда есть абсолютный каталог, даже если script запускается командами, такими как. /script.sh, которые без cd/pwd часто дают просто.. Бесполезно, если script делает cd позже.
Ответ 13
Попробуйте наш новый 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. Попробуйте его на разных платформах и помогите улучшить его.
Ответ 14
Проблема с 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
}
Обратите внимание, что этот вариант также имеет не, чтобы путь существовал.
Ответ 15
Основываясь на ответе @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
Ответ 16
Сегодня я обнаружил, что вы можете использовать команду stat
для разрешения путей.
Итак, для каталога, такого как "~/Documents":
Вы можете запустить это:
stat -f %N ~/Documents
Чтобы получить полный путь:
/Users/me/Documents
Для символических ссылок вы можете использовать опцию формата% Y:
stat -f %Y example_symlink
Что может вернуть результат, например:
/usr/local/sbin/example_symlink
Параметры форматирования могут отличаться в других версиях * NIX, но они работали для меня в OSX.
Ответ 17
Основанный на отличном фрагменте python loveborg, я написал это:
#!/bin/sh
# Version of readlink that follows links to the end; good for Mac OS X
for file in "[email protected]"; 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
Ответ 18
Простое решение с использованием node.js
:
#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));