Tôi muốn chuyển đổi /foo/bar/..
thành /foo
Có một lệnh bash nào làm điều này?
Chỉnh sửa: trong trường hợp thực tế của tôi, thư mục không tồn tại.
nếu bạn muốn kiểm tra một phần tên tệp từ đường dẫn, "dirname" và "tên cơ sở" là bạn bè của bạn và "realpath" cũng có ích.
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
lựa chọn thay thế
Nếu realpath
không được Shell của bạn hỗ trợ, bạn có thể thử
readlink -f /path/here/..
Cũng thế
readlink -m /path/there/../../
Hoạt động giống như
realpath -s /path/here/../../
trong đó đường dẫn không cần phải tồn tại để được chuẩn hóa.
Tôi không biết nếu có lệnh bash trực tiếp để làm điều này, nhưng tôi thường làm
normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"
và nó hoạt động tốt.
Hãy thử realpath
. Dưới đây là toàn bộ nguồn, từ đây được tặng cho phạm vi công cộng.
// 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);
}
Một giải pháp di động và đáng tin cậy là sử dụng python, được cài đặt sẵn khá nhiều ở mọi nơi (bao gồm cả Darwin). Bạn có hai lựa chọn:
abspath
trả về một đường dẫn tuyệt đối nhưng không giải quyết các liên kết tượng trưng:
python -c "import os,sys; print os.path.abspath(sys.argv[1])" path/to/file
realpath
trả về một đường dẫn tuyệt đối và khi làm như vậy sẽ giải quyết các liên kết tượng trưng, tạo ra một đường dẫn chính tắc:
python -c "import os,sys; print os.path.realpath(sys.argv[1])" path/to/file
Trong mỗi trường hợp, path/to/file
có thể là đường dẫn tương đối hoặc tuyệt đối.
Sử dụng tiện ích readlink từ gói coreutils.
MY_PATH=$(readlink -f "$0")
readlink
là tiêu chuẩn bash để có được đường dẫn tuyệt đối. Nó cũng có lợi thế là trả về các chuỗi trống nếu các đường dẫn hoặc một đường dẫn không tồn tại (được cung cấp các cờ để làm như vậy).
Để có được đường dẫn tuyệt đối đến một thư mục có thể tồn tại hoặc không tồn tại, nhưng cha mẹ của ai có tồn tại, hãy sử dụng:
abspath=$(readlink -f $path)
Để có được đường dẫn tuyệt đối đến một thư mục phải tồn tại cùng với tất cả các bậc cha mẹ:
abspath=$(readlink -e $path)
Để chuẩn hóa đường dẫn đã cho và theo các liên kết tượng trưng nếu chúng tồn tại, nhưng nếu không thì bỏ qua các thư mục bị thiếu và dù sao cũng chỉ trả về đường dẫn:
abspath=$(readlink -m $path)
Nhược điểm duy nhất là readlink sẽ theo liên kết. Nếu bạn không muốn theo liên kết, bạn có thể sử dụng quy ước thay thế này:
abspath=$(cd ${path%/*} && echo $PWD/${path##*/})
Điều đó sẽ chdir đến phần thư mục của $ path và in thư mục hiện tại cùng với phần tệp của $ path. Nếu nó không thành công, bạn nhận được một chuỗi rỗng và lỗi trên thiết bị lỗi chuẩn.
Câu hỏi cũ, nhưng có cách đơn giản hơn nhiều nếu bạn đang xử lý tên đường dẫn đầy đủ ở cấp độ Shell:
abspath = "$ (cd" $ đường dẫn "&& pwd)"
Vì cd xảy ra trong một subshell, nó không ảnh hưởng đến tập lệnh chính.
Hai biến thể, giả sử các lệnh tích hợp Shell của bạn chấp nhận -L và -P, là:
abspath = "$ (cd -P" $ path "&& pwd -P)" #physical path với các liên kết tượng trưng được giải quyết abspath = "$ (cd -L" $ path "&& pwd -L)" #logical path bảo tồn các liên kết tượng trưng
Cá nhân, tôi hiếm khi cần cách tiếp cận sau này trừ khi tôi say mê với các liên kết tượng trưng vì một số lý do.
FYI: biến thể trong việc lấy thư mục bắt đầu của tập lệnh hoạt động ngay cả khi tập lệnh thay đổi thư mục hiện tại sau này.
name0 = "$ (tên cơ sở" $ 0 ")"; #base tên của tập lệnh dir0 = "$ (cd" $ (dirname "$ 0") "&& pwd)"; #absolute bắt đầu dir
Việc sử dụng CD đảm bảo bạn luôn có thư mục tuyệt đối, ngay cả khi tập lệnh được chạy bởi các lệnh như ./script.sh, mà không có cd/pwd, thường chỉ cung cấp cho
Như Adam Liss đã lưu ý realpath
không được gói cùng với mọi bản phân phối. Đó là một sự xấu hổ, bởi vì đó là giải pháp tốt nhất. Mã nguồn được cung cấp là tuyệt vời, và tôi có thể sẽ bắt đầu sử dụng nó ngay bây giờ. Đây là những gì tôi đã sử dụng cho đến bây giờ, mà tôi chia sẻ ở đây chỉ để hoàn thiện:
get_abs_path() {
local PARENT_DIR=$(dirname "$1")
cd "$PARENT_DIR"
local ABS_PATH="$(pwd)"/"$(basename "$1")"
cd - >/dev/null
echo "$ABS_PATH"
}
Nếu bạn muốn nó giải quyết các liên kết tượng trưng, chỉ cần thay thế pwd
bằng pwd -P
.
Giải pháp gần đây của tôi là:
pushd foo/bar/..
dir=`pwd`
popd
Dựa trên câu trả lời của Tim Whitcomb.
Không chính xác là một câu trả lời nhưng có lẽ là một câu hỏi tiếp theo (câu hỏi ban đầu không rõ ràng):
readlink
là tốt nếu bạn thực sự muốn theo liên kết tượng trưng. Nhưng cũng có một trường hợp sử dụng để chỉ bình thường hóa các chuỗi ./
và ../
và //
, có thể được thực hiện hoàn toàn bằng cú pháp, mà không chuẩn hóa các liên kết. readlink
không tốt cho việc này và realpath
cũng không tốt.
for f in $paths; do (cd $f; pwd); done
làm việc cho các đường dẫn hiện có, nhưng phá vỡ cho những người khác.
Tập lệnh sed
dường như là một lựa chọn tốt, ngoại trừ việc bạn không thể lặp lại các chuỗi (/foo/bar/baz/../..
-> /foo/bar/..
-> /foo
) mà không sử dụng một cái gì đó như Perl, không an toàn để giả định trên tất cả các hệ thống hoặc sử dụng một số vòng lặp xấu để so sánh đầu ra của sed
cho đầu vào của nó.
FWIW, một lớp lót sử dụng 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
Nói nhiều, và trả lời hơi muộn. Tôi cần phải viết một cái kể từ khi tôi bị mắc kẹt trên RHEL4/5 ..
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`
Tôi đến bữa tiệc muộn, nhưng đây là giải pháp tôi đã tạo ra sau khi đọc một loạt các chủ đề như thế này:
resolve_dir() {
(builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}
Điều này sẽ giải quyết đường dẫn tuyệt đối của $ 1, chơi Nice với ~, giữ liên kết tượng trưng trong đường dẫn của chúng và nó sẽ không gây rối với ngăn xếp thư mục của bạn. Nó trả về đường dẫn đầy đủ hoặc không có gì nếu nó không tồn tại. Nó hy vọng $ 1 sẽ là một thư mục và có thể sẽ thất bại nếu không, nhưng đó là một kiểm tra dễ dàng để tự làm.
Hãy thử sản phẩm thư viện Bash mới của chúng tôi realpath-lib mà chúng tôi đã đặt trên GitHub để sử dụng miễn phí và không bị cản trở. Nó được ghi chép kỹ lưỡng và làm cho một công cụ học tập tuyệt vời.
Nó giải quyết các đường dẫn cục bộ, tương đối và tuyệt đối và không có bất kỳ phụ thuộc nào ngoại trừ Bash 4+; Vì vậy, nó nên làm việc bất cứ nơi nào. Nó miễn phí, sạch sẽ, đơn giản và mang tính hướng dẫn.
Bạn có thể làm:
get_realpath <absolute|relative|symlink|local file path>
Chức năng này là cốt lõi của thư viện:
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
}
Nó cũng chứa các hàm để get_dirname, get_filename, get_ thân tên và validate_path. Hãy thử nó trên các nền tảng và giúp cải thiện nó.
Dựa trên câu trả lời của @ Andre, tôi có thể có một phiên bản tốt hơn một chút, trong trường hợp ai đó đang theo giải pháp dựa trên thao tác chuỗi hoàn toàn không có vòng lặp. Nó cũng hữu ích cho những người không muốn hủy đăng ký bất kỳ liên kết tượng trưng nào, đó là nhược điểm của việc sử dụng realpath
hoặc readlink -f
.
Nó hoạt động trên các phiên bản bash 3.2.25 trở lên.
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
Vấn đề với realpath
là nó không khả dụng trên BSD (hoặc OSX cho vấn đề đó). Đây là một công thức đơn giản được trích xuất từ một bài viết khá cũ (2009) từ Tạp chí Linux , khá dễ mang theo:
function normpath() {
# Remove all /./ sequences.
local path=${1//\/.\//\/}
# Remove dir/.. sequences.
while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
path=${path/${BASH_REMATCH[0]}/}
done
echo $path
}
Lưu ý rằng biến thể này cũng không không yêu cầu đường dẫn tồn tại.
Tôi cần một giải pháp sẽ làm cả ba:
realpath
và readlink -f
là các addonKhông có câu trả lời nào có cả # 1 và # 2. Tôi đã thêm số 3 để cứu người khác bất kỳ việc cạo râu nào.
#!/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")"
Dưới đây là một trường hợp thử nghiệm ngắn với một số không gian xoắn trong các đường dẫn để thực hiện đầy đủ trích dẫn
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 "
Dựa trên đoạn trích trăn tuyệt vời của loveborg, tôi đã viết điều này:
#!/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
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)
Điều này hoạt động ngay cả khi tập tin không tồn tại. Nó không yêu cầu thư mục chứa tập tin tồn tại.
Tôi biết đây là một câu hỏi cổ xưa. Tôi vẫn đang cung cấp một sự thay thế. Gần đây tôi đã gặp vấn đề tương tự và thấy không có lệnh di động và hiện có để làm điều đó. Vì vậy, tôi đã viết kịch bản Shell sau đây bao gồm một hàm có thể thực hiện thủ thuật.
#! /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