Caminho absoluto do script Bash com OS X

98

Estou tentando obter o caminho absoluto para o script em execução no OS X.

Eu vi muitas respostas acontecendo readlink -f $0. No entanto, como o OS X readlinké igual ao do BSD, ele simplesmente não funciona (funciona com a versão do GNU).

Existe uma solução out-of-the-box para isso?

O poderoso pato de borracha
fonte

Respostas:

88

Há uma realpath()função C que fará o trabalho, mas não estou vendo nada disponível na linha de comando. Aqui está uma substituição rápida e suja:

#!/bin/bash

realpath() {
    [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

realpath "$0"

Isso imprime o caminho literalmente se ele começar com a /. Caso contrário, deve ser um caminho relativo, portanto, antecede $PWDà frente. A #./parte é removida ./da frente $1.

John Kugelman
fonte
1
Também notei a função C, mas não consegui encontrar nenhum binário correspondente ou nada. De qualquer forma, sua função se encaixa perfeitamente nas minhas necessidades. Obrigado!
The Mighty Rubber Duck
7
Observe que isso não cancela a referência dos links simbólicos.
Adam Vandenberg
17
realpath ../somethingretorna$PWD/../something
Lri
Isso funcionou para mim, mas mudei o nome para "realpath_osx ()" porque POSSO precisar enviar este script bash para um shell do Linux e não queria que ele colidisse com o realpath "real"! (Tenho certeza de que há uma maneira mais elegante, mas eu sou um bash n00b.)
Andrew Theken
8
command -v realpath >/dev/null 2>&1 || realpath() { ... }
Kara Brightwell
115

Essas três etapas simples vão resolver este e muitos outros problemas do OS X:

  1. Instalar Homebrew
  2. brew install coreutils
  3. grealpath .

(3) pode ser alterado para apenas realpath, consulte (2) saída

Oleg Mikheev
fonte
3
Isso resolve o problema, mas requer a instalação de coisas que você pode não querer ou precisar, ou que podem não estar no sistema de destino, ou seja, OS X
Jason S
6
@JasonS GNU Coreutils é bom demais para não ser usado. Inclui muitos utilitários excelentes. É tão bom que o kernel do Linux é inútil sem ele e porque algumas pessoas o chamam de GNU / Linux. Coreutils é incrível. excelente resposta esta deve ser a resposta selecionada.
7
@omouse A questão menciona especificamente 'uma solução pronta para o uso' (para OS X). Independentemente de quão incrível seja o coreutils, ele não está pronto para uso no OS X, então a resposta não é boa. Não é uma questão de quão bom é o coreutils, ou se é melhor do que o que está no OS X. Existem soluções 'out of the box' que $( cd "$(dirname "$0")" ; pwd -P )funcionam bem para mim.
Jason S
2
Um dos "recursos" do OS X é que o diretório e os nomes dos arquivos não diferenciam maiúsculas de minúsculas. Como resultado, se você tem um diretório chamado XXXe alguém cd xxx, em seguida, pwdirá retornar .../xxx. Exceto por essa resposta, todas as soluções acima retornam xxxquando o que você realmente deseja XXX. Obrigado!
Andrew
1
Não sei como copiar, colar e reinventar código indefinidamente é melhor do que uma solução de gerenciador de pacotes existente. Se você precisava realpath, o que acontece quando é quase certo que você vai precisar de outros itens coreutils? Reescrever essas funções no bash também? : P
Ezekiel Victor
28

Ugh. Achei as respostas anteriores um pouco inadequadas por alguns motivos: em particular, elas não resolvem vários níveis de links simbólicos e são extremamente "Bash-y". Embora a pergunta original peça explicitamente um "script Bash", ela também menciona o Mac OS X, semelhante ao BSD, não GNU readlink. Então aqui está uma tentativa de alguma portabilidade razoável (eu verifiquei com bash como 'sh' e traço), resolvendo um número arbitrário de links simbólicos; e também deve funcionar com espaços em branco no (s) caminho (s), embora eu não tenha certeza do comportamento se houver um espaço em branco no nome base do próprio utilitário, então talvez, hum, evitar isso?

#!/bin/sh
realpath() {
  OURPWD=$PWD
  cd "$(dirname "$1")"
  LINK=$(readlink "$(basename "$1")")
  while [ "$LINK" ]; do
    cd "$(dirname "$LINK")"
    LINK=$(readlink "$(basename "$1")")
  done
  REALPATH="$PWD/$(basename "$1")"
  cd "$OURPWD"
  echo "$REALPATH"
}
realpath "$@"

Espero que isso possa ser útil para alguém.

Geoff Nixon
fonte
1
Eu recomendaria usar apenas localpara variáveis ​​definidas dentro da função para não poluir o namespace global. Ex local OURPWD=.... Funciona pelo menos para o bash.
Michael Paesold
Além disso, o código não deve usar letras maiúsculas para variáveis ​​privadas. Variáveis ​​em maiúsculas são reservadas para uso do sistema.
tripleee
Obrigado pelo script. Caso o (s) link (s) e o arquivo real tenham nomes de base diferentes, provavelmente seria uma boa ideia adicionar um BASENAME=$(basename "$LINK") dentro do while e usá-lo no segundo setter LINK e no setter
REALPATH
Isso não lida com links simbólicos e referências de ..pais exatamente como o realpathfaria. Com o homebrew coreutilsinstalado, tente ln -s /var/log /tmp/linkexampleentão realpath /tmp/linkexample/../; isso imprime /private/var. Mas sua função produz em /tmp/linkexample/..vez disso, porque ..não é um link simbólico.
Martijn Pieters
10

Uma variante mais amigável da linha de comando da solução Python:

python -c "import os; print(os.path.realpath('$1'))"
nanav yorbiz
fonte
2
Apenas no caso de alguém ser louco o suficiente para iniciar um interpretador Python para um único comando ...
Bachsau
Eu estava louco o suficiente, mas usadopython -c "import os; import sys; print(os.path.realpath(sys.argv[1]))"
Alex Chamberlain
7

Eu estava procurando uma solução para usar em um script de provisionamento de sistema, ou seja, rodar antes mesmo de o Homebrew ser instalado. Na falta de uma solução adequada, eu apenas transferia a tarefa para uma linguagem de plataforma cruzada, por exemplo, Perl:

script_abspath=$(perl -e 'use Cwd "abs_path"; print abs_path(@ARGV[0])' -- "$0")

Mais frequentemente, o que realmente queremos é o diretório que o contém:

here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")
4ae1e1
fonte
Ótimo! Perl é bom para a pequena sobrecarga! Você provavelmente poderia simplificar a primeira versão para FULLPATH=$(perl -e "use Cwd 'abs_path'; print abs_path('$0')"). Alguma razão contra?
F Pereira
@FPereira Gerar código de programa a partir de uma string fornecida pelo usuário sem escape nunca é uma boa ideia. ''não é à prova de balas. Quebra se $0contiver aspas simples, por exemplo. Um exemplo muito simples: experimente sua versão em /tmp/'/test.sh, e chame /tmp/'/test.shpor seu caminho completo.
4ae1e1
Ou ainda mais simples /tmp/'.sh,.
4ae1e1
6

Uma vez que existe um caminho real como outros apontaram:

// realpath.c
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char* argv[])
{
  if (argc > 1) {
    for (int argIter = 1; argIter < argc; ++argIter) {
      char *resolved_path_buffer = NULL;
      char *result = realpath(argv[argIter], resolved_path_buffer);

      puts(result);

      if (result != NULL) {
        free(result);
      }
    }
  }

  return 0;
}

Makefile:

#Makefile
OBJ = realpath.o

%.o: %.c
      $(CC) -c -o $@ $< $(CFLAGS)

realpath: $(OBJ)
      gcc -o $@ $^ $(CFLAGS)

Em seguida, compile makee coloque um link simbólico com:
ln -s $(pwd)/realpath /usr/local/bin/realpath

WaffleSouffle
fonte
podemos apenas fazer gcc realpath.c -o /usr/local/bin/realpath?
Alexander Mills
1
@AlexanderMills Você não deve executar o compilador como root; e então se você não fizer isso, você não terá os privilégios para escrever/usr/local/bin
tripleee
6

Use Python para obtê-lo:

#!/usr/bin/env python
import os
import sys

print(os.path.realpath(sys.argv[1]))
acrazing
fonte
2
abs_path () {    
   echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
}

dirnamefornecerá o nome do diretório de /path/to/file, ou seja /path/to.

cd /path/to; pwd garante que o caminho seja absoluto.

basenamefornecerá apenas o nome do arquivo em /path/to/file, ou seja file.

Logu
fonte
Embora este código possa responder à pergunta, fornecer contexto adicional sobre por que e / ou como este código responde à pergunta melhora seu valor a longo prazo.
Igor F.
1

Como você pode ver acima, experimentei isso há cerca de 6 meses. Eu me esqueci totalmente sobre isso até que eu me descobri precisando de algo semelhante novamente. Fiquei completamente chocado ao ver o quão rudimentar era; Tenho me ensinado a programar intensamente por cerca de um ano, mas muitas vezes sinto que talvez não tenha aprendido nada quando as coisas estão no pior.

Eu removeria a 'solução' acima, mas eu realmente gosto dela ser um registro do quanto eu realmente aprendi nos últimos meses.

Mas estou divagando. Sentei-me e resolvi tudo ontem à noite. A explicação nos comentários deve ser suficiente. Se você deseja rastrear a cópia na qual estou trabalhando, pode seguir o seguinte. Isso provavelmente faz o que você precisa.

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi
Geoff Nixon
fonte
1
O link principal parece quebrado.
Alex
esta resposta funciona: stackoverflow.com/a/46772980/1223975 .... você deve apenas usar isso.
Alexander Mills
Observe que sua implementação não resolve links simbólicos antes das ..referências pai; por exemplo, /foo/link_to_other_directory/..é resolvido como /fooem vez do pai de qualquer caminho para o qual o link simbólico /foo/link_to_other_directoryaponta. readlink -fe realpathresolva cada componente do caminho começando na raiz e atualizando os destinos de link anteriores para o restante ainda sendo processado. Eu adicionei uma resposta a esta pergunta, reimplementando essa lógica.
Martijn Pieters
1

realpath para Mac OS X

realpath() {
    path=`eval echo "$1"`
    folder=$(dirname "$path")
    echo $(cd "$folder"; pwd)/$(basename "$path"); 
}

Exemplo com caminho relacionado:

realpath "../scripts/test.sh"

Exemplo com pasta pessoal

realpath "~/Test/../Test/scripts/test.sh"
Evgeny Karpov
fonte
1
boa solução simples, acabei de encontrar uma advertência, ao invocá-lo com ..ele não produz a resposta certa, então adicionei uma verificação se o caminho fornecido é um diretório: if test -d $path ; then echo $(cd "$path"; pwd) ; else [...]
herbert
Não funcionou para mim, "$(dirname $(dirname $(realpath $0)))"mas funciona, então preciso de outra coisa ...
Alexander Mills
O uso inútil deecho também não deve ser perpetrado.
tripleee
Na verdade, isso não resolve os links simbólicos da mesma forma que o realpath faria. Ele resolve ..as referências dos pais antes de resolver os links simbólicos, não depois; tente fazer isso com o homebrew coreutilsinstalado, crie um link com ln -s /var/log /tmp/linkexamplee execute realpath /tmp/linkexample/../; isso imprime /private/var. Mas sua função produz em /tmp/linkexample/..vez disso, porque pwdainda aparece /tmp/linkexampleapós o cd.
Martijn Pieters
1

No macOS, a única solução que encontrei para isso que lida com links simbólicos de maneira confiável é usando realpath. Como isso exige brew install coreutils, eu apenas automatizei essa etapa. Minha implementação é assim:

#!/usr/bin/env bash

set -e

if ! which realpath >&/dev/null; then
  if ! which brew >&/dev/null; then
    msg="ERROR: This script requires brew. See https://brew.sh for installation instructions."
    echo "$(tput setaf 1)$msg$(tput sgr0)" >&2
    exit 1
  fi
  echo "Installing coreutils/realpath"
  brew install coreutils >&/dev/null
fi

thisDir=$( dirname "`realpath "$0"`" )
echo "This script is run from \"$thisDir\""


Este erro ocorrerá se eles não tiverem brewinstalado, mas você também pode instalar apenas isso. Eu simplesmente não me sentia confortável automatizando algo que retira código Ruby arbitrário da rede.

Observe que esta é uma variação automática da resposta de Oleg Mikheev .


Um teste importante

Um bom teste de qualquer uma dessas soluções é:

  1. coloque o código em um arquivo de script em algum lugar
  2. em outro diretório, symlink ( ln -s) para esse arquivo
  3. execute o script desse link simbólico

A solução remove a referência do link simbólico e fornece o diretório original? Se for assim, funciona.

Clay Bridges
fonte
0

Isso parece funcionar para OSX, não requer nenhum binário e foi extraído daqui

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

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

Eu gosto disso:

#!/usr/bin/env bash
function realpath() {
    local _X="$PWD"
    local _LNK=$1
    cd "$(dirname "$_LNK")"
    if [ -h "$_LNK" ]; then
        _LNK="$(readlink "$_LNK")"
        cd "$(dirname "$_LNK")"
    fi
    echo "$PWD/$(basename "$_LNK")"
    cd "$_X"
}
dcmorse
fonte
0

Eu precisava de uma realpathsubstituição no OS X, que funcionasse corretamente em caminhos com links simbólicos e referências de pais exatamente como readlink -ffaria . Isso inclui resolver links simbólicos no caminho antes de resolver as referências dos pais; por exemplo, se você instalou a coreutilsgarrafa homebrew , execute:

$ ln -s /var/log/cups /tmp/linkeddir  # symlink to another directory
$ greadlink -f /tmp/linkeddir/..      # canonical path of the link parent
/private/var/log

Observe que readlink -ffoi resolvido /tmp/linkeddir antes de resolver a ..referência do dir pai. Claro, não há nenhum readlink -fno Mac também .

Portanto, como parte da implementação de um bash para, realpathreimplementei o que uma canonicalize_filename_mode(path, CAN_ALL_BUT_LAST)chamada de função GNUlib faz , no Bash 3.2; esta é também a chamada de função que o GNU readlink -ffaz:

# shellcheck shell=bash
set -euo pipefail

_contains() {
    # return true if first argument is present in the other arguments
    local elem value

    value="$1"
    shift

    for elem in "$@"; do 
        if [[ $elem == "$value" ]]; then
            return 0
        fi
    done
    return 1
}

_canonicalize_filename_mode() {
    # resolve any symlink targets, GNU readlink -f style
    # where every path component except the last should exist and is
    # resolved if it is a symlink. This is essentially a re-implementation
    # of canonicalize_filename_mode(path, CAN_ALL_BUT_LAST).
    # takes the path to canonicalize as first argument

    local path result component seen
    seen=()
    path="$1"
    result="/"
    if [[ $path != /* ]]; then  # add in current working dir if relative
        result="$PWD"
    fi
    while [[ -n $path ]]; do
        component="${path%%/*}"
        case "$component" in
            '') # empty because it started with /
                path="${path:1}" ;;
            .)  # ./ current directory, do nothing
                path="${path:1}" ;;
            ..) # ../ parent directory
                if [[ $result != "/" ]]; then  # not at the root?
                    result="${result%/*}"      # then remove one element from the path
                fi
                path="${path:2}" ;;
            *)
                # add this component to the result, remove from path
                if [[ $result != */ ]]; then
                    result="$result/"
                fi
                result="$result$component"
                path="${path:${#component}}"
                # element must exist, unless this is the final component
                if [[ $path =~ [^/] && ! -e $result ]]; then
                    echo "$1: No such file or directory" >&2
                    return 1
                fi
                # if the result is a link, prefix it to the path, to continue resolving
                if [[ -L $result ]]; then
                    if _contains "$result" "${seen[@]+"${seen[@]}"}"; then
                        # we've seen this link before, abort
                        echo "$1: Too many levels of symbolic links" >&2
                        return 1
                    fi
                    seen+=("$result")
                    path="$(readlink "$result")$path"
                    if [[ $path = /* ]]; then
                        # if the link is absolute, restart the result from /
                        result="/"
                    elif [[ $result != "/" ]]; then
                        # otherwise remove the basename of the link from the result
                        result="${result%/*}"
                    fi
                elif [[ $path =~ [^/] && ! -d $result ]]; then
                    # otherwise all but the last element must be a dir
                    echo "$1: Not a directory" >&2
                    return 1
                fi
                ;;
        esac
    done
    echo "$result"
}

Inclui detecção de link simbólico circular, saindo se o mesmo caminho (intermediário) for visto duas vezes.

Se tudo que você precisa é readlink -f, então você pode usar o acima como:

readlink() {
    if [[ $1 != -f ]]; then  # poor-man's option parsing
        # delegate to the standard readlink command
        command readlink "$@"
        return
    fi

    local path result seenerr
    shift
    seenerr=
    for path in "$@"; do
        # by default readlink suppresses error messages
        if ! result=$(_canonicalize_filename_mode "$path" 2>/dev/null); then
            seenerr=1
            continue
        fi
        echo "$result"
    done
    if [[ $seenerr ]]; then
        return 1;
    fi
}

Pois realpath, eu também precisava --relative-toe --relative-basesuporte, que fornecem caminhos relativos após a canonização:

_realpath() {
    # GNU realpath replacement for bash 3.2 (OS X)
    # accepts --relative-to= and --relative-base options
    # and produces canonical (relative or absolute) paths for each
    # argument on stdout, errors on stderr, and returns 0 on success
    # and 1 if at least 1 path triggered an error.

    local relative_to relative_base seenerr path

    relative_to=
    relative_base=
    seenerr=

    while [[ $# -gt 0 ]]; do
        case $1 in
            "--relative-to="*)
                relative_to=$(_canonicalize_filename_mode "${1#*=}")
                shift 1;;
            "--relative-base="*)
                relative_base=$(_canonicalize_filename_mode "${1#*=}")
                shift 1;;
            *)
                break;;
        esac
    done

    if [[
        -n $relative_to
        && -n $relative_base
        && ${relative_to#${relative_base}/} == "$relative_to"
    ]]; then
        # relative_to is not a subdir of relative_base -> ignore both
        relative_to=
        relative_base=
    elif [[ -z $relative_to && -n $relative_base ]]; then
        # if relative_to has not been set but relative_base has, then
        # set relative_to from relative_base, simplifies logic later on
        relative_to="$relative_base"
    fi

    for path in "$@"; do
        if ! real=$(_canonicalize_filename_mode "$path"); then
            seenerr=1
            continue
        fi

        # make path relative if so required
        if [[
            -n $relative_to
            && ( # path must not be outside relative_base to be made relative
                -z $relative_base || ${real#${relative_base}/} != "$real"
            )
        ]]; then
            local common_part parentrefs

            common_part="$relative_to"
            parentrefs=
            while [[ ${real#${common_part}/} == "$real" ]]; do
                common_part="$(dirname "$common_part")"
                parentrefs="..${parentrefs:+/$parentrefs}"
            done

            if [[ $common_part != "/" ]]; then
                real="${parentrefs:+${parentrefs}/}${real#${common_part}/}"
            fi
        fi

        echo "$real"
    done
    if [[ $seenerr ]]; then
        return 1
    fi
}

if ! command -v realpath > /dev/null 2>&1; then
    # realpath is not available on OSX unless you install the `coreutils` brew
    realpath() { _realpath "$@"; }
fi

Incluí testes de unidade em minha solicitação de revisão de código para este código .

Martijn Pieters
fonte
-2

Com base na comunicação com o comentarista, concordei que é muito difícil e não há uma maneira trivial de implementar um realpath que se comporte totalmente da mesma forma que o Ubuntu.

Mas a versão a seguir, pode lidar com casos de canto, a melhor resposta não pode e satisfazer minhas necessidades diárias no macbook. Coloque este código em seu ~ / .bashrc e lembre-se:

  • arg só pode ser 1 arquivo ou dir, sem curinga
  • sem espaços no diretório ou nome do arquivo
  • pelo menos o arquivo ou diretório pai do dir existe
  • sinta-se à vontade para usar. .. / coisa, estes são seguros

    # 1. if is a dir, try cd and pwd
    # 2. if is a file, try cd its parent and concat dir+file
    realpath() {
     [ "$1" = "" ] && return 1

     dir=`dirname "$1"`
     file=`basename "$1"`

     last=`pwd`

     [ -d "$dir" ] && cd $dir || return 1
     if [ -d "$file" ];
     then
       # case 1
       cd $file && pwd || return 1
     else
       # case 2
       echo `pwd`/$file | sed 's/\/\//\//g'
     fi

     cd $last
    }
occia
fonte
Você deseja evitar o uso inútil deecho . Apenas pwdfaz o mesmo que echo $(pwd)sem ter que gerar uma segunda cópia do shell. Além disso, não citar o argumento de echoé um bug (você perderá qualquer espaço em branco à esquerda ou à direita, qualquer caractere de espaço em branco interno adjacente e terá os curingas expandidos, etc). Veja mais stackoverflow.com/questions/10067266/…
tripleee
Além disso, o comportamento de caminhos inexistentes é problemático; mas acho que é isso que a frase "mas lembre-se" talvez esteja tentando dizer. Embora o comportamento no Ubuntu certamente não seja imprimir o diretório atual quando você solicita o realpatharquivo de um diretório inexistente.
tripleee
Para consistência, provavelmente prefira em dir=$(dirname "$1"); file=$(basename "$1")vez da sintaxe de crase obsoleta. Observe também a citação correta de argumentos, novamente.
tripleee
Sua resposta atualizada parece não conseguir corrigir muitos dos bugs e adiciona outros.
tripleee
por favor me dê alguns casos específicos de falha, porque todos os testes que eu faço no desktop ubuntu 18.04 estão ok, obrigado.
outubro