คุณทำให้เส้นทางไฟล์เป็นปกติใน Bash ได้อย่างไร


203

ฉันต้องการที่จะแปลง/foo/bar/..เป็น/foo

มีคำสั่ง bash ซึ่งทำเช่นนี้?


แก้ไข: ในกรณีที่ปฏิบัติของฉันไดเรกทอรีที่มีอยู่


4
มันมีความสำคัญหาก/foo/barหรือแม้กระทั่ง/fooมีอยู่จริงหรือคุณสนใจเฉพาะในด้านการจัดการสตริงตามกฎชื่อพา ธ
เฉินเลวี

5
@twalberg ... มันเป็นเรื่องที่คิด ...
Camilo Martin

2
@CamiloMartin ไม่ได้วางแผนเลย - ทำสิ่งที่คำถามถาม - แปลง/foo/bar/..เป็น/fooและใช้bashคำสั่ง หากมีข้อกำหนดอื่น ๆ ที่ไม่ได้ระบุไว้บางทีพวกเขาควรจะเป็น ...
twalberg

14
@twalberg คุณทำ TDD -_- 'มากเกินไป
Camilo Martin

คำตอบ:


192

หากคุณต้องการที่จะ 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 ทางเลือก

หากrealpathเชลล์ของคุณไม่รองรับคุณสามารถลองใช้ได้

readlink -f /path/here/.. 

ด้วย

readlink -m /path/there/../../ 

ทำงานเหมือนกัน

realpath -s /path/here/../../

ในเส้นทางที่ไม่จำเป็นต้องอยู่ที่จะทำให้เป็นมาตรฐาน


5
สำหรับผู้ที่ต้องการโซลูชัน OS X ลองดูคำตอบของ Adam Liss ด้านล่าง
เทรนตัน

stackoverflow.com/a/17744637/999943 นี่คือคำตอบที่เกี่ยวข้องอย่างยิ่ง! ฉันเจอโพสต์ QA ทั้งสองในวันเดียวและฉันต้องการที่จะเชื่อมโยงเข้าด้วยกัน
phyatt

realpath ดูเหมือนจะได้รับการเพิ่ม coreutils ในปี 2012 ดูประวัติของไฟล์ที่github.com/coreutils/coreutils/commits/master/src/realpath.c
Noah Lavine

1
ทั้งสองrealpathและreadlinkมาจากยูทิลิตี้หลักของ GNU ดังนั้นส่วนใหญ่คุณอาจได้ทั้งหรือไม่ ถ้าฉันจำได้อย่างถูกต้องรุ่น readlink บน Mac นั้นจะแตกต่างจาก GNU เล็กน้อย: - \
Antoine 'hashar' Musso

100

ฉันไม่ทราบว่ามีคำสั่งทุบตีโดยตรงที่จะทำเช่นนี้ แต่ฉันมักจะทำ

normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"

และมันทำงานได้ดี


9
การทำเช่นนี้จะทำให้ปกติ แต่ไม่สามารถแก้ไขลิงก์ที่ไม่ต้องการได้ นี่อาจเป็นได้ทั้งบั๊กหรือฟีเจอร์ :-)
Adam Liss

4
นอกจากนี้ยังมีปัญหาหากมีการกำหนด $ CDPATH เนื่องจาก "cd foo" จะเปลี่ยนเป็นไดเรกทอรี "foo" ใด ๆ ที่เป็นไดเรกทอรีย่อยของ $ CDPATH ไม่ใช่เฉพาะ "foo" ที่อยู่ในไดเรกทอรีปัจจุบัน ฉันคิดว่าคุณต้องทำอะไรเช่น: CDPATH = "" cd "$ {dirToNormalize}" && pwd -P
mjs

7
คำตอบของทิมนั้นง่ายและสะดวกที่สุด CDPATH นั้นง่ายต่อการจัดการ: dir = "$ (ยกเลิกการตั้งค่า CDPATH && cd" $ dir "&& pwd)"
David Blevins

2
นี่อาจเป็นอันตรายมาก ( rm -rf $normalDir) หากไม่มี dirToNormalize!
Frank Meulenaar

4
ใช่มันอาจจะเป็นการดีที่สุดถ้าจะใช้&&ความคิดเห็นของ @ DavidBlevins
Elias Dorneles

54

ลอง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);
}

1
สิ่งนี้รวมอยู่ในการติดตั้งทุบตีมาตรฐานหรือไม่? ฉันได้รับคำสั่ง "ไม่พบ" บนระบบของเรา (ทุบตี 3.00.15 (1) ใน RHEL)
ทิมวิทคอมบ์

4
gnu.org/software/coreutilsสำหรับ readlink แต่ realpath มาจากแพ็คเกจนี้: packages.debian.org/unstable/utils/realpath
Kent Fredric

8
เป็นมาตรฐานสำหรับ Ubuntu / BSD ไม่ใช่ Centos / OSX
Erik Aronesty

4
คุณควรเข้ามามีส่วนร่วมด้วย "โบนัส: คำสั่ง bash และมีให้ทุกที่!" realpathส่วนที่เกี่ยวกับ นอกจากนี้ยังเป็นที่ชื่นชอบของฉัน แต่แทนที่จะรวบรวมเครื่องมือของคุณจากแหล่งที่มา มันคือทั้งหมดที่มีน้ำหนักมากและส่วนใหญ่เวลาที่คุณต้องการreadlink -f... BTW readlinkยังไม่ทุบตีในตัว แต่เป็นส่วนหนึ่งของcoreutilsUbuntu
Tomasz Gandor

4
คุณบอกว่าคุณบริจาคสิ่งนี้ให้กับโดเมนสาธารณะ แต่ส่วนหัวก็บอกว่า "สำหรับการใช้ที่ไม่หวังผลกำไร" - คุณหมายถึงการบริจาคให้กับโดเมนสาธารณะหรือไม่? นั่นหมายความว่ามันสามารถใช้เพื่อวัตถุประสงค์ทางการค้าเพื่อผลกำไรเช่นเดียวกับที่ไม่หวังผลกำไร…
me_and

36

โซลูชันแบบพกพาและเชื่อถือได้คือการใช้ python ซึ่งติดตั้งล่วงหน้าแล้วในทุกที่ (รวมถึงดาร์วิน) คุณมีสองทางเลือก:

  1. abspath ส่งคืนพา ธ สัมบูรณ์ แต่ไม่สามารถแก้ไข symlink:

    python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

  2. realpath ส่งคืนพา ธ สัมบูรณ์และในการดำเนินการแก้ไข symlink สร้างพา ธ แบบบัญญัติ:

    python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file

ในแต่ละกรณีpath/to/fileสามารถเป็นเส้นทางแบบสัมพันธ์หรือแบบสัมบูรณ์


7
ขอบคุณนั่นเป็นสิ่งเดียวที่ใช้ได้ readlink หรือ realpath ไม่พร้อมใช้งานใน OS X Python ควรอยู่บนแพลตฟอร์มส่วนใหญ่
sorin

1
เพียงชี้แจงreadlinkก็มีให้ใน OS X ไม่ใช่ด้วย-fตัวเลือก วิธีการแก้ปัญหาแบบพกพากล่าวถึงที่นี่
StvnW

3
แท้จริงมันกวนใจของฉันว่านี่เป็นทางออกที่มีเหตุผลเพียงอย่างเดียวหากคุณไม่ต้องการติดตามลิงก์ ทาง Unix วางเท้าของฉัน
DannoHung

34

ใช้ยูทิลิตี readlink จากแพ็คเกจ coreutils

MY_PATH=$(readlink -f "$0")

1
BSD ไม่มีแฟล็ก -f ซึ่งหมายความว่าสิ่งนี้จะล้มเหลวแม้ใน MacOS Mojave ล่าสุดและระบบอื่น ๆ อีกมากมาย ลืมเกี่ยวกับการใช้ -f หากคุณต้องการความสะดวกในการพกพา OS-es จำนวนมากได้รับผลกระทบ
sorin

1
@sorin คำถามไม่ได้เกี่ยวกับ Mac แต่เกี่ยวกับ Linux
mattalxndr

14

readlinkเป็นมาตรฐานทุบตีสำหรับการได้รับเส้นทางที่แน่นอน นอกจากนี้ยังมีข้อได้เปรียบของการคืนค่าสตริงว่างหากไม่มีเส้นทางหรือเส้นทาง (ให้แฟล็กทำเช่นนั้น)

ในการรับพา ธ สัมบูรณ์ไปยังไดเร็กทอรีที่อาจมีหรือไม่มีอยู่ แต่มีใครเป็นผู้ปกครองให้ใช้:

abspath=$(readlink -f $path)

ในการรับพา ธ สัมบูรณ์ไปยังไดเร็กทอรีที่ต้องมีอยู่พร้อมกับพาเรนต์ทั้งหมด:

abspath=$(readlink -e $path)

หากต้องการกำหนดเส้นทางที่กำหนดและติดตาม symlink หากมีอยู่จริง แต่ไม่เช่นนั้นให้ข้ามไดเรกทอรีที่หายไปและกลับเส้นทางใหม่

abspath=$(readlink -m $path)

ข้อเสียเพียงอย่างเดียวคือ readlink จะติดตามลิงก์ หากคุณไม่ต้องการติดตามลิงก์คุณสามารถใช้ระเบียบปฏิบัติทางเลือกนี้:

abspath=$(cd ${path%/*} && echo $PWD/${path##*/})

ที่จะ chdir ไปยังส่วนไดเรกทอรีของ $ path และพิมพ์ไดเรกทอรีปัจจุบันพร้อมกับส่วนไฟล์ของ $ path หากล้มเหลวในการ chdir คุณจะได้รับสตริงว่างและข้อผิดพลาดใน stderr


7
readlinkเป็นตัวเลือกที่ดีถ้ามี เวอร์ชั่น OS X ไม่รองรับ-eหรือ-fตัวเลือก ในสามตัวอย่างแรกคุณควรมีเครื่องหมายอัญประกาศคู่ล้อมรอบ$pathเพื่อจัดการช่องว่างหรือสัญลักษณ์แทนในชื่อไฟล์ +1 สำหรับการขยายพารามิเตอร์ แต่มีช่องโหว่ด้านความปลอดภัย หากpathว่างเปล่าสิ่งนี้จะcdไปยังไดเรกทอรีบ้านของคุณ คุณต้องมีเครื่องหมายคำพูดคู่ abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
toxalot

นี่เป็นเพียงตัวอย่าง หากคุณรู้สึกแย่กับเรื่องความปลอดภัยคุณก็ไม่ควรใช้ bash หรือ shell อื่น ๆ เลย นอกจากนี้ทุบตีมีปัญหาของตัวเองเมื่อมันมาถึงความเข้ากันได้ข้ามแพลตฟอร์มเช่นเดียวกับการมีปัญหากับการเปลี่ยนแปลงการทำงานระหว่างรุ่นใหญ่ OSX เป็นเพียงหนึ่งในหลาย ๆ แพลตฟอร์มที่มีปัญหาที่เกี่ยวข้องกับการเขียนสคริปต์เชลล์ไม่ต้องพูดถึงมันขึ้นอยู่กับ BSD เมื่อคุณต้องเป็นแพลตฟอร์มที่แท้จริงอย่างแท้จริงคุณจะต้องเป็นไปตาม POSIX ดังนั้นการขยายพารามิเตอร์จะออกไปนอกหน้าต่างจริงๆ ดู Solaris หรือ HP-UX สักครู่
Craig

6
ไม่ได้หมายถึงความผิดใด ๆ ที่นี่ แต่ชี้ให้เห็นปัญหาที่คลุมเครือเช่นนี้เป็นสิ่งสำคัญ ฉันแค่ต้องการคำตอบอย่างรวดเร็วสำหรับปัญหาเล็ก ๆ น้อย ๆ นี้และฉันจะเชื่อถือรหัสนั้นด้วยการป้อนข้อมูลใด ๆ / ทั้งหมดถ้ามันไม่ได้สำหรับความคิดเห็นข้างต้น สิ่งสำคัญคือต้องสนับสนุน OS-X ในการสนทนาทุบตีเหล่านี้ มีคำสั่งมากมายที่ไม่ได้รับการสนับสนุนบน OS-X และมีฟอรัมมากมายที่อนุญาตเมื่อพูดคุยกับ Bash ซึ่งหมายความว่าเราจะยังคงได้รับปัญหาข้ามแพลตฟอร์มจำนวนมากจนกว่าจะได้รับการจัดการในไม่ช้า
Rebs

13

คำถามเก่า แต่มีวิธีที่ง่ายกว่ามากถ้าคุณกำลังจัดการกับชื่อพา ธ แบบเต็มระดับเชลล์:

   abspath = "$ (cd" $ path "&& pwd)"

เนื่องจาก cd เกิดขึ้นใน subshell จึงไม่มีผลกับสคริปต์หลัก

สองรูปแบบสมมติว่าคำสั่งในตัวเชลล์ของคุณยอมรับ -L และ -P คือ:

   abspath = "$ (cd -P" $ path "&& pwd -P)" #physical path พร้อม symlink ที่ได้รับการแก้ไข
   abspath = "$ (cd -L" $ path "&& pwd -L)" #logical path รักษา symlink

โดยส่วนตัวแล้วฉันไม่ค่อยต้องการวิธีการนี้ในภายหลังเว้นแต่ฉันจะรู้สึกทึ่งกับลิงก์สัญลักษณ์ด้วยเหตุผลบางประการ

FYI: ความหลากหลายของการได้รับไดเรกทอรีเริ่มต้นของสคริปต์ที่ใช้งานได้แม้ว่าสคริปต์จะเปลี่ยนเป็นไดเรกทอรีปัจจุบันในภายหลัง

name0 = "$ (basename" $ ​​0 ")"; #base ชื่อของสคริปต์
dir0 = "$ (cd" $ (dirname "$ 0") "&& pwd)"; #absolute dir เริ่มต้น

การใช้ซีดีช่วยให้คุณมั่นใจได้ว่าจะมีไดเรกทอรีที่สมบูรณ์เสมอแม้ว่าสคริปต์จะถูกเรียกใช้โดยคำสั่งเช่น. /script.sh ซึ่งหากไม่มี cd / pwd มักจะให้เพียงแค่ .. ไร้ประโยชน์หากสคริปต์ใช้ซีดีในภายหลัง


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"
} 

หากคุณต้องการให้ symlinks แก้ไขเพียงแทนที่ด้วยpwdpwd -P


หนึ่ง gotcha ที่มีpwd -Pตัวเลือกสำหรับกรณีนี้ ... พิจารณาว่าจะเกิดอะไรขึ้นถ้า$(basename "$1")เป็น symlink ไปยังไฟล์ในไดเรกทอรีอื่น pwd -Pแก้ไขเพียง symlinks ในส่วนไดเรกทอรีของเส้นทาง แต่ไม่ได้ส่วน basename
toxalot

7

ทางออกล่าสุดของฉันคือ:

pushd foo/bar/..
dir=`pwd`
popd

ตามคำตอบของ Tim Whitcomb


ฉันสงสัยว่าสิ่งนี้จะล้มเหลวหากอาร์กิวเมนต์ไม่ใช่ไดเรกทอรี สมมติว่าฉันต้องการทราบที่ / usr / bin / java นำไปสู่?
Edward Falk

1
หากคุณรู้ว่ามันคือไฟล์คุณสามารถpushd $(dirname /usr/bin/java)ลองได้
schmunk

5

ไม่ใช่คำตอบที่แน่นอน แต่อาจเป็นคำถามติดตาม (คำถามเดิมไม่ชัดเจน):

readlinkไม่เป็นไรถ้าคุณต้องการติดตาม symlinks แต่ยังมีกรณีการใช้งานสำหรับเพียง normalizing ./และ../และ//ลำดับซึ่งสามารถทำได้อย่างหมดจดไวยากรณ์, โดยไม่ต้องกำลังยอมรับ symlinks readlinkไม่ดีสำหรับสิ่งนี้และไม่เป็นrealpathเช่นนั้น

for f in $paths; do (cd $f; pwd); done

ใช้งานได้สำหรับเส้นทางที่มีอยู่ แต่แบ่งให้ผู้อื่น

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

realpathมี-sตัวเลือกที่จะไม่สามารถแก้ไขการเชื่อมโยงสัญลักษณ์และมีเพียงการแก้ปัญหาการอ้างอิงถึง/./, /../และลบพิเศษ/ตัวอักษร เมื่อรวมกับ-mตัวเลือกจะrealpathทำงานเฉพาะกับชื่อไฟล์และไม่ได้สัมผัสไฟล์จริงใด ๆ มันเสียงเหมือนโซลูชั่นที่สมบูรณ์แบบ แต่อนิจจาrealpathก็ยังขาดอยู่ในหลาย ๆ ระบบ
toxalot

การลบ..คอมโพเนนต์ไม่สามารถทำได้โดยใช้ syntactically เมื่อมีการเชื่อมโยง symlink /one/two/../threeไม่เหมือนกันเช่น/one/threeถ้าtwoเป็น symlink /foo/barไป
jrw32982 รองรับ Monica

@ jrw32982 ใช่ตามที่ระบุไว้ในคำตอบของฉันนี่เป็นกรณีการใช้งานเมื่อไม่ต้องการหรือจำเป็นต้องใช้ symlink
Jesse Glick

@JesseGlick ไม่ใช่เพียงแค่กรณีที่คุณต้องการระบุสัญลักษณ์ symicalize หรือไม่ อัลกอริทึมของคุณสร้างคำตอบที่ผิด เพื่อให้คำตอบของคุณถูกต้องคุณจะต้องรู้ก่อนว่าไม่มีการเชื่อมโยงที่เกี่ยวข้อง (หรือเป็นเพียงรูปแบบเดียว) คำตอบของคุณบอกว่าคุณไม่ต้องการบัญญัติให้พวกเขาไม่ใช่ว่าไม่มี symlinks ที่เกี่ยวข้องในเส้นทาง
jrw32982 รองรับ Monica

มีกรณีการใช้งานที่ต้องทำการปรับสภาพให้เป็นมาตรฐานโดยไม่ต้องสมมติโครงสร้างไดเรกทอรีที่มีอยู่คงที่ การปรับมาตรฐาน URI นั้นใกล้เคียงกัน ในกรณีเหล่านี้เป็นข้อ จำกัด โดยเนื้อแท้ที่ผลลัพธ์มักจะไม่ถูกต้องหากมี symlink อยู่ใกล้ไดเรกทอรีซึ่งผลลัพธ์ถูกนำไปใช้ในภายหลัง
Jesse Glick

5

ฉันไปงานปาร์ตี้ช้า แต่นี่เป็นวิธีการแก้ปัญหาที่ฉันสร้างขึ้นหลังจากอ่านเธรดจำนวนมากเช่นนี้:

resolve_dir() {
        (builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}

สิ่งนี้จะแก้ไขเส้นทางที่แน่นอนของ $ 1 เล่นได้ดีกับ ~ เก็บ symlink ในเส้นทางที่พวกเขาอยู่และมันจะไม่ยุ่งกับสแต็คไดเรกทอรีของคุณ ส่งคืนพา ธ เต็มหรือไม่มีอะไรเลยหากไม่มีอยู่ คาดว่า $ 1 จะเป็นไดเรกทอรีและอาจล้มเหลวหากไม่ใช่ แต่นั่นเป็นการตรวจสอบที่ง่ายที่จะทำด้วยตัวเอง


4

ช่างพูดและตอบช้าไปหน่อย ฉันต้องเขียนมันตั้งแต่ฉันติดอยู่กับ 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`

3

ลองใช้ผลิตภัณฑ์real-lib- Bash ของห้องสมุด Bash ใหม่ที่เราวางไว้บน 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 ลองใช้ข้ามแพลตฟอร์มและช่วยในการปรับปรุง


2

ปัญหา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
}

ขอให้สังเกตว่าตัวแปรนี้ยังไม่ต้องการเส้นทางที่มีอยู่


แต่นี่ไม่ได้แก้ลิงก์ Realpath ประมวลผลพา ธ ที่เริ่มต้นที่รูทและติดตาม symlink ตามที่มันดำเนินไป ทั้งหมดนี้ทำคือยุบการอ้างอิงผู้ปกครอง
Martijn Pieters

2

จากคำตอบของ @ Andre ฉันอาจมีเวอร์ชั่นที่ดีกว่าเล็กน้อยในกรณีที่มีใครบางคนอยู่หลังการแก้ปัญหาโดยใช้การจัดการสตริงแบบวนรอบ นอกจากนี้ยังเป็นประโยชน์สำหรับผู้ที่ไม่ต้องการที่จะ dereference symlinks ใด ๆ ซึ่งเป็นข้อเสียของการใช้หรือrealpathreadlink -f

มันใช้งานได้กับเวอร์ชั่นทุบตี 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

นี่เป็นความคิดที่ดี แต่ฉันเสียเวลา 20 นาทีในการพยายามทำสิ่งนี้ให้ทำงานกับ bash รุ่นต่างๆ ปรากฎว่าตัวเลือก extglob shell นั้นจำเป็นต้องเปิดใช้งานเพื่อให้สามารถใช้งานได้และไม่ได้เป็นค่าเริ่มต้น เมื่อพูดถึงการทำงานของ bash เป็นสิ่งสำคัญที่จะต้องระบุทั้งรุ่นที่ต้องการและตัวเลือกที่ไม่ใช่ค่าเริ่มต้นเนื่องจากรายละเอียดเหล่านี้อาจแตกต่างกันระหว่างระบบปฏิบัติการ ตัวอย่างเช่น Mac OSX รุ่นล่าสุด (Yosemite) มาพร้อมกับ bash รุ่นที่ล้าสมัย (3.2)
drwatsoncode

ขออภัย @ricovox; ฉันได้ปรับปรุงสิ่งเหล่านี้แล้ว ฉันอยากรู้รุ่นที่แน่นอนของ Bash ที่คุณอยู่ที่นั่น สูตรด้านบน (อัพเดท) ทำงานบน CentOS 5.8 ซึ่งมาพร้อมกับ bash 3.2.25
ϹοδεMεδιϲ

ขอโทษสำหรับความสับสน. รหัสนี้ใช้งานได้กับ Mac OSX ทุบตีของฉัน (3.2.57) เมื่อฉันเปิด extglob บันทึกย่อของฉันเกี่ยวกับรุ่นทุบตีนั้นเป็นคำทั่วไปมากกว่า (ซึ่งจริง ๆ แล้วนำไปใช้กับคำตอบอื่นที่นี่เกี่ยวกับ regex ในการทุบตี)
drwatsoncode

2
ฉันขอขอบคุณคำตอบของคุณ ฉันใช้มันเป็นฐานสำหรับฉันเอง ฉันสังเกตเห็นหลายกรณีที่คุณล้มเหลว: (1) เส้นทางสัมพัทธ์hello/../world(2) จุดในชื่อไฟล์/hello/..world(3) จุดหลังจาก double-slash /hello//../world(4) จุดก่อนหรือหลัง double-slash /hello//./worldหรือ /hello/.//world (5) ผู้ปกครองหลังปัจจุบัน: /hello/./../world/ (6) ผู้ปกครอง หลังจากที่ผู้ปกครอง: /hello/../../worldฯลฯ - บางเหล่านี้สามารถแก้ไขได้โดยใช้ห่วงที่จะทำให้การแก้ไขจนเส้นทางหยุดการเปลี่ยนแปลง (ลบdir/../เช่นกัน /dir/..ยกเว้น แต่ลบออกdir/..จากตอนท้าย)
drwatsoncode

0

จากตัวอย่างของ 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

0
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

สิ่งนี้ใช้ได้แม้ว่าไฟล์จะไม่มีอยู่ มันต้องการไดเรกทอรีที่มีไฟล์อยู่


GNU Realpath ไม่ต้องการองค์ประกอบสุดท้ายในเส้นทางที่จะมีอยู่เว้นแต่ว่าคุณจะใช้realpath -e
Martijn Pieters

0

ฉันต้องการโซลูชันที่จะทำทั้งสามอย่าง:

  • ทำงานบน Mac สต็อก realpathและreadlink -fเป็น addons
  • แก้ไข symlink
  • มีข้อผิดพลาดในการจัดการ

ไม่มีคำตอบใดที่มีทั้ง # 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 "

0

ฉันรู้ว่านี่เป็นคำถามโบราณ ฉันยังคงเสนอทางเลือกอื่น เมื่อเร็ว ๆ นี้ฉันพบปัญหาเดียวกันและไม่พบคำสั่งที่มีอยู่และพกพาได้ ดังนั้นฉันจึงเขียนเชลล์สคริปต์ต่อไปนี้ซึ่งรวมถึงฟังก์ชั่นที่สามารถหลอกลวงได้

#! /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


0

ฉันสร้างฟังก์ชันเฉพาะในตัวเพื่อจัดการสิ่งนี้โดยมุ่งเน้นที่ประสิทธิภาพสูงสุดที่เป็นไปได้ (เพื่อความสนุกสนาน) มันไม่สามารถแก้ symlinks realpath -smดังนั้นจึงเป็นพื้นเดียวกับ

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () { 
  ${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "${s:-/}";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;
}

หมายเหตุ: ฟังก์ชั่นนี้ไม่ได้จัดการเส้นทางที่ขึ้นต้นด้วย//เนื่องจากสองสแลชสองเท่าที่เริ่มต้นของเส้นทางเป็นพฤติกรรมที่กำหนดโดยการนำไปปฏิบัติ แต่ก็จัดการ/, ///และอื่น ๆ เพียงแค่ปรับ

ฟังก์ชั่นนี้ดูเหมือนว่าจะจัดการกับกรณีขอบทั้งหมดอย่างถูกต้อง แต่อาจมีบางอย่างที่ฉันไม่ได้จัดการ

ผลการดำเนินงานหมายเหตุ: เมื่อเรียกที่มีมากมายของข้อโต้แย้งabspathวิ่ง 10x ช้ากว่าrealpath -sm; เมื่อเรียกด้วยอาร์กิวเมนต์ตัวเดียวให้abspathรัน> 110x เร็วกว่าrealpath -smบนเครื่องของฉันส่วนใหญ่เนื่องจากไม่ต้องการรันโปรแกรมใหม่ทุกครั้ง


-1

ฉันค้นพบวันนี้ว่าคุณสามารถใช้statคำสั่งเพื่อแก้ไขเส้นทาง

ดังนั้นสำหรับไดเรกทอรีเช่น "~ / Documents":

คุณสามารถเรียกใช้สิ่งนี้:

stat -f %N ~/Documents

ในการรับเส้นทางแบบเต็ม:

/Users/me/Documents

สำหรับ symlink คุณสามารถใช้ตัวเลือกรูปแบบ% Y:

stat -f %Y example_symlink

ซึ่งอาจส่งคืนผลลัพธ์เช่น:

/usr/local/sbin/example_symlink

ตัวเลือกการจัดรูปแบบอาจแตกต่างกับ * NIX เวอร์ชันอื่น แต่สิ่งเหล่านี้ใช้ได้กับฉันใน OSX


1
stat -f %N ~/Documentsบรรทัดเป็นปลาชนิดหนึ่งสีแดง ... เปลือกของคุณจะถูกแทนที่~/Documentsด้วย/Users/me/Documentsและstatเป็นเพียงพิมพ์คำต่อคำโต้แย้ง
danwyand

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.