web-dev-qa-db-ja.com

Bashでファイルパスをどのように正規化しますか?

/foo/bar/../fooに変換したい

これを行うbashコマンドはありますか?


編集:私の実際のケースでは、ディレクトリは存在します。

179
Fabien

パスからファイル名の一部を削除したい場合は、「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/../../

正規化するためにパスが存在する必要はありません。

172
Kent Fredric

これを行うための直接bashコマンドがあるかどうかはわかりませんが、通常は

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

そしてそれはうまく機能します。

93
Tim Whitcomb

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);
}
53
Adam Liss

ポータブルで信頼性の高いソリューションは、Pythonを使用することです。これは、至る所(Darwinを含む)にプリインストールされています。次の2つのオプションがあります。

  1. abspathは絶対パスを返しますが、シンボリックリンクを解決しません:

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

  2. realpathは絶対パスを返し、そうすることでシンボリックリンクを解決し、標準パスを生成します。

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

いずれの場合も、path/to/fileは相対パスでも絶対パスでもかまいません。

35
loevborg

Coreutilsパッケージのreadlinkユーティリティを使用します。

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

readlinkは、絶対パスを取得するためのbash標準です。また、パスまたはパスが存在しない場合に空の文字列を返すという利点もあります(そうするためのフラグが与えられた場合)。

存在するかもしれないし、存在しないかもしれないが、親は存在するディレクトリへの絶対パスを取得するには、次を使用します:

abspath=$(readlink -f $path)

すべての親とともに存在する必要があるディレクトリへの絶対パスを取得するには:

abspath=$(readlink -e $path)

指定されたパスを正規化し、存在する場合はシンボリックリンクをたどりますが、そうでない場合は、欠落しているディレクトリを無視して、とにかくパスを返します。

abspath=$(readlink -m $path)

唯一の欠点は、readlinkがリンクをたどるということです。リンクをたくない場合は、次の代替規則を使用できます。

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

これは、$ pathのディレクトリ部分にchdirし、$ pathのファイル部分とともに現在のディレクトリを出力します。 chdirに失敗すると、空の文字列とstderrでエラーが発生します。

13
Craig

古い質問ですが、もっと簡単な方法があります フルパス名を扱っている場合 シェルレベルで:

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

Cdはサブシェルで発生するため、メインスクリプトには影響しません。

シェルの組み込みコマンドが-Lと-Pを受け入れると仮定すると、2つのバリエーションがあります。

 abspath = "$(cd -P" $ path "&& pwd -P)"#解決されたシンボリックリンクを持つ物理パス
 abspath = "$(cd -L" $ path "&& pwd -L ) "#論理パス保存シンボリックリンク

個人的には、何らかの理由でシンボリックリンクに魅了されない限り、この後のアプローチはほとんど必要ありません。

参考までに、スクリプトが後で現在のディレクトリを変更しても機能する、スクリプトの開始ディレクトリの取得に関するバリエーション。

name0 = "$(basename" $ 0 ")"; #スクリプトのベース名
 dir0 = "$(cd" $(dirname "$ 0") "&& pwd)"; #絶対開始ディレクトリ

CDを使用すると、スクリプトが./script.shなどのコマンドで実行され、cd/pwdなしでは多くの場合。

10
Gilbert

Adam Lissが述べたように、realpathはすべてのディストリビューションにバンドルされているわけではありません。それが最善の解決策だからです。提供されたソースコードは素晴らしいものであり、おそらく今から使い始めます。ここに私がこれまで使用してきたものを示しますが、完全を期すためにここで共有しています。

get_abs_path() {
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
} 

シンボリックリンクを解決する場合は、pwdpwd -Pに置き換えるだけです。

7
Jeet

私の最近の解決策は:

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

ティムウィットコムの回答に基づきます。

7
schmunk

正確な答えではなく、おそらくフォローアップの質問(元の質問は明示的ではありませんでした):

readlinkは、実際にシンボリックリンクを追跡する場合は問題ありません。しかし、単に./シーケンスと../シーケンスと//シーケンスを正規化するユースケースもあります。これは、純粋に構文的に行うことができ、withoutシンボリックリンクを正規化します。 readlinkはこれに適しておらず、realpathも同様です。

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

既存のパスでは機能しますが、他のパスでは機能しません。

sedスクリプトは、Perlのようなものを使用せずにシーケンス(/foo/bar/baz/../..-> /foo/bar/..-> /foo)を繰り返し置き換えることができないことを除いて、良い方法のようです。すべてのシステムで想定したり、someいループを使用して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
5
Jesse Glick

おしゃべり、そして少し遅い答え。古いRHEL4/5にこだわっているので、1つ書く必要があります。絶対リンクと相対リンクを処理し、//、/。/、および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`
4
alhernau

私はパーティーに遅れていますが、これはこのようなスレッドの束を読んだ後、私が作成したソリューションです:

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

これにより、$ 1の絶対パスが解決され、〜でNiceが再生され、シンボリックリンクが存在するパスに保持され、ディレクトリスタックが混乱することはありません。完全なパスを返すか、存在しない場合は何も返しません。 $ 1がディレクトリであると想定し、そうでない場合はおそらく失敗しますが、それは簡単に確認できます。

4
apottere

新しい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の関数も含まれています。プラットフォーム間で試してみて、改善に役立ててください。

3
AsymLabs

@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
2

realpathの問題は、BSD(またはOSX)では利用できないことです。 Linux Journalのかなり古い(2009)記事 から抽出した簡単なレシピを次に示します。

function normpath() {
  # Remove all /./ sequences.
  local path=${1//\/.\//\/}

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}

このバリアントは、パスが存在することをnotも必要とすることに注意してください。

1
André Anjos

3つすべてを実行するソリューションが必要でした。

  • ストックMacで作業します。 realpathreadlink -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 "
0
David Blevins

Loveborgの優れたpythonスニペットに基づいて、私はこれを書きました:

#!/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
Edward Falk
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

これは、ファイルが存在しない場合でも機能します。ファイルを含むディレクトリが存在する必要があります。

0
user240515

これは古代の質問です。私はまだ代替手段を提供しています。最近、私は同じ問題に出会い、それを行うための既存の移植可能なコマンドが見つからなかった。そこで、このトリックを実行できる関数を含む次のシェルスクリプトを作成しました。

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