Cara mengambil path absolut yang diberikan relatif


160

Apakah ada perintah untuk mengambil jalur absolut yang diberikan jalur relatif?

Sebagai contoh saya ingin $ line berisi path absolut dari setiap file dalam dir ./etc/

find ./ -type f | while read line; do
   echo $line
done


1
bukan duplikat stright, tetapi serupa
mpapis


Solusi yang jauh lebih baik daripada yang ada di sini sejauh ini adalah di sini bagaimana cara mengkonversi relatif-path-ke-absolut-path-in-unix
Daniel Genin

Jawaban:


67

menggunakan:

find "$(pwd)"/ -type f

untuk mendapatkan semua file atau

echo "$(pwd)/$line"

untuk menampilkan jalur lengkap (jika jalur relatif penting)


2
bagaimana jika saya menentukan jalur relatif di find ??
nubme

17
kemudian lihat tautan dalam komentar untuk pertanyaan Anda, cara terbaik mungkin adalah 'readlink -f $ line'
mpapis

3
Mengapa digunakan $(pwd)sebagai pengganti $PWD? (ya, saya tahu pwdadalah builtin)
Adam Katz

178

Coba realpath.

~ $ sudo apt-get install realpath  # may already be installed
~ $ realpath .bashrc
/home/username/.bashrc

Untuk menghindari perluasan symlink, gunakan realpath -s.

Jawabannya datang dari " perintah bash / fish untuk mencetak path absolut ke file ".


11
realpathsepertinya tidak tersedia di Mac (OS X 10.11 "El Capitan"). :-(
Laryx Decidua

realpathtampaknya tidak tersedia di CentOS 6 baik
user5359531

6
di osx, brew install coreutilsakan membawarealpath
Kevin Chen

Di Ubuntu 18.04 saya, realpathsudah ada. Saya tidak perlu menginstalnya secara terpisah.
Acumenus

Anehnya, realpathtersedia di Git untuk Windows (bagi saya, setidaknya).
Andrew Keeton

104

Jika Anda telah menginstal paket coreutils, Anda biasanya dapat menggunakan readlink -f relative_file_nameuntuk mengambil yang absolut (dengan semua symlinks diselesaikan)


3
Perilaku untuk ini sedikit berbeda dari apa yang diminta pengguna, itu juga akan mengikuti dan menyelesaikan symlink rekursif di mana saja di jalan. Anda mungkin tidak menginginkannya dalam beberapa kasus.
meraba

1
@BradPeabody Ini berfungsi di Mac jika Anda menginstal coreutils dari homebrew brew install coreutils. Namun eksekusi dieksekusi oleh ag:greadlink -f relative_file_name
Miguel Isla

2
Perhatikan bahwa halaman manual readlink (1) memiliki kalimat pertama dari deskripsi: "Note realpath (1) adalah perintah yang lebih disukai untuk digunakan untuk fungsionalitas kanonikisasi."
josch

1
Anda dapat menggunakan -e bukannya -f untuk memeriksa apakah file / direktori ada atau tidak
Iceman

69
#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

UPD Beberapa penjelasan

  1. Script ini mendapatkan path relatif sebagai argumen "$1"
  2. Kemudian kita mendapatkan bagian dirname dari jalur itu (Anda bisa mengirimkan dir atau file ke skrip ini):dirname "$1"
  3. Kemudian kita cd "$(dirname "$1")menuju dir relatif ini dan mendapatkan path absolut untuk itu dengan menjalankan pwdperintah shell
  4. Setelah itu kita tambahkan nama samaran ke path absolut:$(basename "$1")
  5. Sebagai langkah terakhir kita echoitu

8
Jawaban ini menggunakan terbaik Bash idiom
Rondo

2
readlink adalah solusi sederhana untuk linux, tetapi solusi ini juga berfungsi pada OSX, jadi +1
thetoolman

1
Skrip ini tidak setara dengan apa realpathatau apa yang readlink -fdilakukan. Misalnya, itu tidak berfungsi di jalur di mana komponen terakhir adalah symlink.
josch

2
@ josch: Pertanyaannya bukan tentang penyelesaian symlink. Tetapi jika Anda ingin melakukan itu, Anda dapat memberikan -Popsi untuk pwdperintah:echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"
Eugen Konkov

4
Saya suka jawabannya, tetapi hanya berfungsi, jika pengguna diizinkan untuk cd ke direktori. Ini mungkin tidak selalu memungkinkan.
Matthias B

34

Untuk apa nilainya, saya memilih jawaban yang dipilih, tetapi ingin berbagi solusi. The downside adalah, itu hanya Linux - Saya menghabiskan sekitar 5 menit mencoba untuk menemukan setara OSX sebelum datang ke Stack overflow. Saya yakin itu di luar sana.

Di Linux Anda dapat menggunakannya readlink -ebersama-sama dirname.

$(dirname $(readlink -e ../../../../etc/passwd))

hasil panen

/etc/

Dan kemudian Anda menggunakan dirnamesaudara perempuan, basenameuntuk mendapatkan nama file

$(basename ../../../../../passwd)

hasil panen

passwd

Menyatukan semuanya ..

F=../../../../../etc/passwd
echo "$(dirname $(readlink -e $F))/$(basename $F)"

hasil panen

/etc/passwd

Anda aman jika Anda menargetkan direktori, tidak basenameakan mengembalikan apa pun dan Anda hanya akan berakhir dengan garis miring ganda pada hasil akhir.


Entri yang sangat baik dengan dirname, readlink, dan basename. Itu membantu saya untuk mendapatkan jalur absolut dari tautan simbolik - bukan targetnya.
kevinarpe

Tidak berfungsi ketika Anda ingin mengembalikan jalur ke tautan simbolik (yang kebetulan harus saya lakukan ...).
Tomáš Zato - Reinstate Monica

Bagaimana Anda bisa menemukan jalur absolut ke jalur yang tidak ada?
synthesizerpatel

@synthesizerpatel Cukup mudah, saya akan berpikir; jika saya masuk /home/GKFXdan saya mengetik touch newfile, maka sebelum saya menekan enter Anda bisa mengetahui maksud saya "create / home / GKFX / newfile", yang merupakan jalur absolut ke file yang belum ada.
GKFX

25

Saya pikir ini yang paling portabel:

abspath() {                                               
    cd "$(dirname "$1")"
    printf "%s/%s\n" "$(pwd)" "$(basename "$1")"
    cd "$OLDPWD"
}

Ini akan gagal jika jalurnya tidak ada.


3
Tidak perlu melakukan cd kembali. Lihat stackoverflow.com/a/21188136/1504556 . Jawaban Anda adalah yang terbaik di halaman ini, IMHO. Bagi yang berminat tautan memberikan penjelasan mengapa solusi ini bekerja.
peterh

Ini tidak terlalu portabel, dirnameadalah utilitas inti GNU, tidak umum untuk semua yang belum saya percaya.
einpoklum

@einpoklum dirnameadalah utilitas standar POSIX, lihat di sini: pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html
Ernest A

Ya Tuhan, terima kasih. Saya telah mencoba untuk memperbaiki versi yang digunakan ${1##*/}untuk sehari sekarang, dan sekarang saya mengganti sampah dengan basename "$1"itu tampaknya akhirnya benar-benar menangani jalur yang berakhiran /.
l3l_aze

NB, ini tidak melakukan hal yang benar dengan jalan berakhir pada../..
Alex Coventry

16

realpath mungkin yang terbaik

Tapi ...

Pertanyaan awal sangat membingungkan untuk memulai, dengan contoh yang buruk terkait dengan pertanyaan seperti yang dinyatakan.

Jawaban yang dipilih sebenarnya menjawab contoh yang diberikan, dan tidak sama sekali pertanyaan dalam judul. Perintah pertama adalah jawaban itu (benarkah? Aku ragu), dan bisa melakukannya tanpa '/'. Dan saya gagal melihat apa yang dilakukan perintah kedua.

Beberapa masalah beragam:

  • mengubah pathname relatif menjadi absolut, apa pun yang ditunjukkan, mungkin tidak ada. ( Biasanya, jika Anda mengeluarkan perintah seperti itu touch foo/bar, nama path foo/barharus ada untuk Anda, dan mungkin digunakan dalam perhitungan, sebelum file tersebut benar-benar dibuat. )

  • mungkin ada beberapa pathname absolut yang menunjukkan file yang sama (atau file potensial), terutama karena tautan simbolik (symlink) di path, tetapi mungkin karena alasan lain (perangkat mungkin dipasang dua kali lebih sebagai hanya baca). Seseorang mungkin atau mungkin tidak ingin menyelesaikan expllink symlinks tersebut.

  • sampai ke ujung rantai tautan simbolik ke file atau nama yang tidak memiliki tautan. Ini mungkin atau mungkin tidak menghasilkan nama jalur absolut, tergantung pada bagaimana hal itu dilakukan. Dan seseorang mungkin, atau mungkin tidak ingin menyelesaikannya menjadi pathname absolut.

Perintah readlink footanpa opsi memberikan jawaban hanya jika argumennya fooadalah tautan simbolik, dan jawaban itu adalah nilai symlink itu. Tidak ada tautan lain yang diikuti. Jawabannya mungkin jalur relatif: berapapun nilai argumen symlink.

Namun, readlinkmemiliki opsi (-f -e atau -m) yang akan bekerja untuk semua file, dan memberikan satu pathname absolut (yang tanpa symlink) ke file yang sebenarnya dilambangkan oleh argumen.

Ini berfungsi dengan baik untuk apa pun yang bukan symlink, meskipun orang mungkin ingin menggunakan pathname absolut tanpa menyelesaikan symlink menengah di path. Ini dilakukan oleh perintahrealpath -s foo

Dalam kasus argumen symlink, readlinkdengan opsinya lagi akan menyelesaikan semua symlink pada path absolut ke argumen, tetapi itu juga akan mencakup semua symlink yang mungkin ditemui dengan mengikuti nilai argumen. Anda mungkin tidak ingin itu jika Anda menginginkan jalur absolut ke argumen symlink itu sendiri, daripada ke apa pun yang ditautkan. Sekali lagi, jika foomerupakan symlink, realpath -s fooakan mendapatkan jalur absolut tanpa menyelesaikan symlink, termasuk yang diberikan sebagai argumen.

Tanpa -sopsi, realpathmelakukan hampir sama readlink, kecuali hanya membaca nilai tautan, serta beberapa hal lainnya. Tidak jelas bagi saya mengapa readlinkmemiliki pilihan, menciptakan redundansi yang tampaknya tidak diinginkan dengan realpath.

Menjelajahi web tidak banyak bicara, kecuali bahwa mungkin ada beberapa variasi di seluruh sistem.

Kesimpulan: realpathadalah perintah terbaik untuk digunakan, dengan fleksibilitas terbanyak, setidaknya untuk penggunaan yang diminta di sini.


10

Solusi favorit saya adalah yang oleh @EugenKonkov karena tidak menyiratkan keberadaan utilitas lain (paket coreutils).

Tetapi gagal untuk jalur relatif "." dan "..", jadi di sini adalah versi yang sedikit ditingkatkan menangani kasus-kasus khusus ini.

Masih gagal jika pengguna tidak memiliki izin untuk cdmasuk ke direktori induk dari jalur relatif.

#! /bin/sh

# Takes a path argument and returns it as an absolute path. 
# No-op if the path is already absolute.
function to-abs-path {
    local target="$1"

    if [ "$target" == "." ]; then
        echo "$(pwd)"
    elif [ "$target" == ".." ]; then
        echo "$(dirname "$(pwd)")"
    else
        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
    fi
}

7

Jawaban Eugen tidak berhasil bagi saya, tetapi ini berhasil:

    absolute="$(cd $(dirname \"$file\"); pwd)/$(basename \"$file\")"

Catatan, direktori kerja Anda saat ini tidak terpengaruh.


Perhatikan: itu skrip. di mana $ 1 adalah argumen pertama untuk itu
Eugen Konkov

5

Solusi terbaik, imho, adalah yang diposting di sini: https://stackoverflow.com/a/3373298/9724628 .

Memang membutuhkan python untuk bekerja, tetapi tampaknya mencakup semua atau sebagian besar kasus tepi dan menjadi solusi yang sangat portabel.

  1. Dengan menyelesaikan symlink:
python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file
  1. atau tanpa itu:
python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

3

Dalam hal find, mungkin paling mudah untuk memberikan jalur absolut untuk mencari, misalnya:

find /etc
find `pwd`/subdir_of_current_dir/ -type f

3

Jika Anda menggunakan bash pada Mac OS X yang tidak memiliki realpath atau readlink -nya tidak dapat mencetak path absolut, Anda mungkin punya pilihan selain mengkode versi Anda sendiri untuk mencetaknya. Inilah implementasi saya:

(pesta murni)

abspath(){
  local thePath
  if [[ ! "$1" =~ ^/ ]];then
    thePath="$PWD/$1"
  else
    thePath="$1"
  fi
  echo "$thePath"|(
  IFS=/
  read -a parr
  declare -a outp
  for i in "${parr[@]}";do
    case "$i" in
    ''|.) continue ;;
    ..)
      len=${#outp[@]}
      if ((len==0));then
        continue
      else
        unset outp[$((len-1))] 
      fi
      ;;
    *)
      len=${#outp[@]}
      outp[$len]="$i"
      ;;
    esac
  done
  echo /"${outp[*]}"
)
}

(gunakan gawk)

abspath_gawk() {
    if [[ -n "$1" ]];then
        echo $1|gawk '{
            if(substr($0,1,1) != "/"){
                path = ENVIRON["PWD"]"/"$0
            } else path = $0
            split(path, a, "/")
            n = asorti(a, b,"@ind_num_asc")
            for(i in a){
                if(a[i]=="" || a[i]=="."){
                    delete a[i]
                }
            }
            n = asorti(a, b, "@ind_num_asc")
            m = 0
            while(m!=n){
                m = n
                for(i=1;i<=n;i++){
                    if(a[b[i]]==".."){
                        if(b[i-1] in a){
                            delete a[b[i-1]]
                            delete a[b[i]]
                            n = asorti(a, b, "@ind_num_asc")
                            break
                        } else exit 1
                    }
                }
            }
            n = asorti(a, b, "@ind_num_asc")
            if(n==0){
                printf "/"
            } else {
                for(i=1;i<=n;i++){
                    printf "/"a[b[i]]
                }
            }
        }'
    fi
}

(aws murni bsd)

#!/usr/bin/env awk -f
function abspath(path,    i,j,n,a,b,back,out){
  if(substr(path,1,1) != "/"){
    path = ENVIRON["PWD"]"/"path
  }
  split(path, a, "/")
  n = length(a)
  for(i=1;i<=n;i++){
    if(a[i]==""||a[i]=="."){
      continue
    }
    a[++j]=a[i]
  }
  for(i=j+1;i<=n;i++){
    delete a[i]
  }
  j=0
  for(i=length(a);i>=1;i--){
    if(back==0){
      if(a[i]==".."){
        back++
        continue
      } else {
        b[++j]=a[i]
      }
    } else {
      if(a[i]==".."){
        back++
        continue
      } else {
        back--
        continue
      }
    }
  }
  if(length(b)==0){
    return "/"
  } else {
    for(i=length(b);i>=1;i--){
      out=out"/"b[i]
    }
    return out
  }
}

BEGIN{
  if(ARGC>1){
    for(k=1;k<ARGC;k++){
      print abspath(ARGV[k])
    }
    exit
  }
}
{
  print abspath($0)
}

contoh:

$ abspath I/am/.//..//the/./god/../of///.././war
/Users/leon/I/the/war

1

Apa yang mereka katakan, kecuali find $PWDatau (dalam bash) find ~+sedikit lebih nyaman.


1

Mirip dengan jawaban @ ernest-a tetapi tanpa memengaruhi $OLDPWDatau mendefinisikan fungsi baru Anda dapat memecat sebuah subkulit(cd <path>; pwd)

$ pwd
/etc/apache2
$ cd ../cups 
$ cd -
/etc/apache2
$ (cd ~/..; pwd)
/Users
$ cd -
/etc/cups

1

Jika jalur relatif adalah jalur direktori, maka coba milikku, harus menjadi yang terbaik:

absPath=$(pushd ../SOME_RELATIVE_PATH_TO_Directory > /dev/null && pwd && popd > /dev/null)

echo $absPath

Solusi ini hanya berfungsi untuk bash, lihat juga stackoverflow.com/a/5193087/712014
Michael

1
echo "mydir/doc/ mydir/usoe ./mydir/usm" |  awk '{ split($0,array," "); for(i in array){ system("cd "array[i]" && echo $PWD") } }'

3
Terima kasih atas cuplikan kode ini, yang mungkin memberikan bantuan jangka pendek terbatas. Penjelasan yang tepat akan sangat meningkatkan nilai jangka panjangnya dengan menunjukkan mengapa ini adalah solusi yang baik untuk masalah ini, dan akan membuatnya lebih bermanfaat bagi pembaca masa depan dengan pertanyaan lain yang serupa. Harap edit jawaban Anda untuk menambahkan beberapa penjelasan, termasuk asumsi yang Anda buat.
Toby Speight

1

Peningkatan ke versi @ ernest-a yang agak bagus:

absolute_path() {
    cd "$(dirname "$1")"
    case $(basename $1) in
        ..) echo "$(dirname $(pwd))";;
        .)  echo "$(pwd)";;
        *)  echo "$(pwd)/$(basename $1)";;
    esac
}

Ini berurusan dengan benar dengan kasus di mana elemen terakhir dari path .., dalam hal ini jawaban "$(pwd)/$(basename "$1")"in @ ernest-a akan muncul sebagai accurate_sub_path/spurious_subdirectory/...


0

Jika Anda ingin mengubah variabel yang berisi jalur relatif menjadi jalur absolut, ini berfungsi:

   dir=`cd "$dir"`

"cd" menggema tanpa mengubah direktori kerja, karena dieksekusi di sini dalam sub-shell.


2
Pada bash-4.3-p46, ini tidak berfungsi: shell mencetak baris kosong ketika saya menjalankandir=`cd ".."` && echo $dir
Michael

0

Ini adalah solusi berantai dari semua yang lain, misalnya, ketika realpathgagal, baik karena tidak diinstal atau karena keluar dengan kode kesalahan, maka, solusi berikutnya dicoba sampai mendapatkan jalur yang benar.

#!/bin/bash

function getabsolutepath() {
    local target;
    local changedir;
    local basedir;
    local firstattempt;

    target="${1}";
    if [ "$target" == "." ];
    then
        printf "%s" "$(pwd)";

    elif [ "$target" == ".." ];
    then
        printf "%s" "$(dirname "$(pwd)")";

    else
        changedir="$(dirname "${target}")" && basedir="$(basename "${target}")" && firstattempt="$(cd "${changedir}" && pwd)" && printf "%s/%s" "${firstattempt}" "${basedir}" && return 0;
        firstattempt="$(readlink -f "${target}")" && printf "%s" "${firstattempt}" && return 0;
        firstattempt="$(realpath "${target}")" && printf "%s" "${firstattempt}" && return 0;

        # If everything fails... TRHOW PYTHON ON IT!!!
        local fullpath;
        local pythoninterpreter;
        local pythonexecutables;
        local pythonlocations;

        pythoninterpreter="python";
        declare -a pythonlocations=("/usr/bin" "/bin");
        declare -a pythonexecutables=("python" "python2" "python3");

        for path in "${pythonlocations[@]}";
        do
            for executable in "${pythonexecutables[@]}";
            do
                fullpath="${path}/${executable}";

                if [[ -f "${fullpath}" ]];
                then
                    # printf "Found ${fullpath}\\n";
                    pythoninterpreter="${fullpath}";
                    break;
                fi;
            done;

            if [[ "${pythoninterpreter}" != "python" ]];
            then
                # printf "Breaking... ${pythoninterpreter}\\n"
                break;
            fi;
        done;

        firstattempt="$(${pythoninterpreter} -c "import os, sys; print( os.path.abspath( sys.argv[1] ) );" "${target}")" && printf "%s" "${firstattempt}" && return 0;
        # printf "Error: Could not determine the absolute path!\\n";
        return 1;
    fi
}

printf "\\nResults:\\n%s\\nExit: %s\\n" "$(getabsolutepath "./asdfasdf/ asdfasdf")" "${?}"

0

Anda bisa menggunakan substitusi string bash untuk jalur $ jalur relatif apa pun:

line=$(echo ${line/#..\//`cd ..; pwd`\/})
line=$(echo ${line/#.\//`pwd`\/})
echo $line

Substitusi front-of-string dasar mengikuti rumus
$ {string / # substring / replacement}
yang dibahas dengan baik di sini: https://www.tldp.org/LDP/abs/html/string-manipulation.html

The \karakter meniadakan /ketika kita ingin menjadi bagian dari string yang kita temukan / ganti.


0

Saya tidak dapat menemukan solusi yang portabel rapi antara Mac OS Catalina, Ubuntu 16 dan Centos 7, jadi saya memutuskan untuk melakukannya dengan python inline dan itu bekerja dengan baik untuk skrip bash saya.

to_abs_path() {
  python -c "import os; print os.path.abspath('$1')"
}

to_abs_path "/some_path/../secrets"
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.