Je veux transformer /foo/bar/..
en /foo
Y at-il une commande bash qui fait cela?
Edit: dans mon cas pratique, le répertoire existe.
si vous voulez supprimer une partie du nom de fichier du chemin, "dirname" et "basename" sont vos amis, et "realpath" est également pratique.
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
alternatives
Si realpath
n'est pas supporté par votre shell, vous pouvez essayer
readlink -f /path/here/..
Également
readlink -m /path/there/../../
Fonctionne comme
realpath -s /path/here/../../
en ce que le chemin n'a pas besoin d'exister pour être normalisé.
Je ne sais pas s'il existe une commande directe bash pour le faire, mais je le fais habituellement
normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"
et ça marche bien.
Essayez realpath
. Ci-dessous, la source dans son intégralité, par la présente, reversée au domaine public.
// 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);
}
Une solution portable et fiable consiste à utiliser python, qui est préinstallé un peu partout (y compris Darwin). Vous avez deux options:
abspath
renvoie un chemin absolu mais ne résout pas les liens symboliques:
python -c "import os,sys; print os.path.abspath(sys.argv[1])" path/to/file
realpath
renvoie un chemin absolu et résout ainsi les liens symboliques, générant un chemin canonique:
python -c "import os,sys; print os.path.realpath(sys.argv[1])" path/to/file
Dans chaque cas, path/to/file
peut être un chemin relatif ou absolu.
Utilisez l'utilitaire readlink du package coreutils.
MY_PATH=$(readlink -f "$0")
readlink
est la norme bash pour obtenir le chemin absolu. Cela a aussi l'avantage de retourner des chaînes vides si un chemin ou un chemin n'existe pas (en fonction des drapeaux nécessaires).
Pour obtenir le chemin absolu d'un répertoire qui peut exister ou non, mais dont les parents existent, utilisez:
abspath=$(readlink -f $path)
Pour obtenir le chemin absolu d'un répertoire qui doit exister avec tous les parents:
abspath=$(readlink -e $path)
Pour canoniser le chemin donné et suivre les liens symboliques s’ils existent, sinon ignorer les répertoires manquants et simplement renvoyer le chemin, c’est:
abspath=$(readlink -m $path)
Le seul inconvénient est que readlink suivra les liens. Si vous ne souhaitez pas suivre les liens, vous pouvez utiliser cette convention alternative:
abspath=$(cd ${path%/*} && echo $PWD/${path##*/})
Cela permettra de passer à la partie répertoire de $ path et d’imprimer le répertoire actuel avec la partie fichier de $ path. Si chdir échoue, vous obtenez une chaîne vide et une erreur sur stderr.
Vieille question, mais il y a beaucoup plus simple si vous avez affaire à des noms de chemins complets au niveau de la coquille:
abspath = "$ (cd" $ path "&& pwd)"
Comme le cd se passe dans un sous-shell, cela n’a pas d’impact sur le script principal.
Deux variantes, en supposant que vos commandes intégrées à Shell acceptent -L et -P, sont les suivantes:
abspath = "$ (cd -P" $ chemin "&& pwd -P)" # chemin physique avec liens symboliques résolus abspath = "$ (cd -L" $ chemin "&& pwd -L)" chemin_logique préservant les liens symboliques
Personnellement, j’ai rarement besoin de cette approche ultérieure à moins que je ne sois fasciné par les liens symboliques pour une raison quelconque.
FYI: variation sur l’obtention du répertoire de départ d’un script qui fonctionne même si le script change son répertoire courant ultérieurement.
name0 = "$ (nom de base" $ 0 ")"; #base nom du script dir0 = "$ (cd" $ (dirname "$ 0") "&& pwd)"; #absolute start dir
L'utilisation de CD garantit que vous avez toujours le répertoire absolu, même si le script est exécuté avec des commandes telles que ./script.sh qui, sans le cd/pwd, donnent souvent la valeur .. Inutile si le script effectue un cd ultérieurement.
Comme Adam Liss l'a noté, la variable realpath
n'est pas fournie avec chaque distribution. Ce qui est dommage, car c'est la meilleure solution. Le code source fourni est excellent, et je vais probablement commencer à l’utiliser maintenant. Voici ce que j'ai utilisé jusqu'à présent, que je partage ici pour être complet:
get_abs_path() {
local PARENT_DIR=$(dirname "$1")
cd "$PARENT_DIR"
local ABS_PATH="$(pwd)"/"$(basename "$1")"
cd - >/dev/null
echo "$ABS_PATH"
}
Si vous voulez résoudre les liens symboliques, remplacez simplement pwd
par pwd -P
.
Ma solution récente était:
pushd foo/bar/..
dir=`pwd`
popd
Basé sur la réponse de Tim Whitcomb.
Pas exactement une réponse, mais peut-être une question de suivi (la question initiale n'était pas explicite):
readlink
convient si vous voulez réellement suivre les liens symboliques. Mais il existe également un cas d'utilisation consistant à normaliser simplement les séquences ./
et ../
et //
, ce qui peut être fait de manière purement syntaxique, sans canonisation des liens symboliques. readlink
n'est pas bon pour cela, et realpath
non plus.
for f in $paths; do (cd $f; pwd); done
fonctionne pour les chemins existants, mais casse pour les autres.
Un script sed
semble être un bon choix, sauf que vous ne pouvez pas remplacer les séquences de manière itérative (/foo/bar/baz/../..
-> /foo/bar/..
-> /foo
) sans utiliser quelque chose comme Perl, qu'il n'est pas prudent d'assumer sur tous les systèmes, ou d'utiliser une boucle laide pour comparer la sortie de sed
à son entrée.
FWIW, one-liner utilisant 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
Réponse bavarde et un peu tardive. Je dois en écrire un car je suis coincé dans d'anciennes RHEL4/5 . Je gère les liens absolus et relatifs, et simplifie les entrées //, /./ et 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`
Je suis en retard pour la fête, mais c'est la solution que j'ai élaborée après avoir lu un tas de sujets comme celui-ci:
resolve_dir() {
(builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}
Cela résoudra le chemin absolu de $ 1, jouera Nice avec ~, gardera les liens symboliques dans le chemin où ils se trouvent et cela ne gâchera pas votre pile de répertoires. Il retourne le chemin complet ou rien s'il n'existe pas. Il s'attend à ce que $ 1 soit un répertoire et échouera probablement si ce n'est pas le cas, mais c'est une vérification facile à faire par vous-même.
Essayez notre nouveau produit de bibliothèque Bash realpath-lib que nous avons placé sur GitHub pour un usage gratuit et sans entrave. Il est parfaitement documenté et constitue un excellent outil d'apprentissage.
Il résout les chemins locaux, relatifs et absolus et n'a aucune dépendance, sauf Bash 4+; donc ça devrait marcher à peu près n'importe où. C'est gratuit, propre, simple et instructif.
Tu peux faire:
get_realpath <absolute|relative|symlink|local file path>
Cette fonction est le noyau de la bibliothèque:
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
}
Il contient également des fonctions telles que get_dirname, get_filename, get_stname et validate_path. Essayez-le sur plusieurs plates-formes et contribuez à l'améliorer.
D'après la réponse de @Andre, je pourrais avoir une version légèrement meilleure, au cas où quelqu'un voudrait une solution sans boucle et entièrement basée sur la manipulation de chaînes. C'est également utile pour ceux qui ne veulent pas déréférencer les liens symboliques, ce qui représente l'inconvénient d'utiliser realpath
ou readlink -f
.
Cela fonctionne sur les versions 3.2.25 et supérieures de bash.
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
Le problème avec realpath
est qu’il n’est pas disponible sous BSD (ni sous OSX). Voici une recette simple extraite de un article assez ancien (2009) de Linux Journal , qui est assez portable:
function normpath() {
# Remove all /./ sequences.
local path=${1//\/.\//\/}
# Remove dir/.. sequences.
while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
path=${path/${BASH_REMATCH[0]}/}
done
echo $path
}
Remarquez que cette variante ne nécessite également pas le {pas} _ chemin d'accès.
J'avais besoin d'une solution qui ferait les trois:
realpath
et readlink -f
sont des addonsAucune des réponses n’avait les numéros 1 et 2. J'ai ajouté le n ° 3 pour sauver les autres du rasage de yak.
#!/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")"
Voici un court cas de test avec quelques espaces tordus dans les chemins pour exercer pleinement la citation
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 "
Basé sur l'excellent extrait de python de loveborg, j'ai écrit ceci:
#!/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
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)
Cela fonctionne même si le fichier n'existe pas. Il faut que le répertoire contenant le fichier existe.
Je sais que c'est une question ancienne. Je propose toujours une alternative. Récemment, j'ai rencontré le même problème et n'ai trouvé aucune commande existante et portable pour le faire. J'ai donc écrit le script Shell suivant, qui inclut une fonction qui peut faire l'affaire.
#! /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