Bash script percorso assoluto con OS X


98

Sto cercando di ottenere il percorso assoluto dello script attualmente in esecuzione su OS X.

Ho visto molte risposte in corso readlink -f $0. Tuttavia, poiché OS X readlinkè uguale a BSD, semplicemente non funziona (funziona con la versione GNU).

Esiste una soluzione immediata a questo problema?





16
$( cd "$(dirname "$0")" ; pwd -P )
Jason S

Risposte:


88

C'è una realpath()funzione C che farà il lavoro, ma non vedo nulla di disponibile sulla riga di comando. Ecco una sostituzione rapida e sporca:

#!/bin/bash

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

realpath "$0"

Questo stampa il percorso alla lettera se inizia con a /. In caso contrario, deve essere un percorso relativo, quindi antepone $PWDall'inizio. La #./parte si stacca ./dalla parte anteriore di $1.


1
Ho anche notato la funzione C ma non sono riuscito a trovare alcun binario corrispondente o altro. Ad ogni modo, la tua funzione si adatta perfettamente alle mie esigenze. Grazie!
The Mighty Rubber Duck

7
Nota che questo non dereferenzia i collegamenti simbolici.
Adam Vandenberg

17
realpath ../somethingritorna$PWD/../something
Lri

Questo ha funzionato per me, ma l'ho rinominato "realpath_osx ()" perché potrei aver bisogno di inviare questo script bash a una shell Linux e non volevo che entrasse in collisione con il "vero" realpath! (Sono sicuro che ci sia un modo più elegante, ma io sono un bash n00b.)
Andrew Theken,

8
command -v realpath >/dev/null 2>&1 || realpath() { ... }
Kara Brightwell

115

Questi tre semplici passaggi risolveranno questo e molti altri problemi di OS X:

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

(3) può essere cambiato in solo realpath, vedere (2) output


3
Questo risolve il problema, ma richiede l'installazione di cose che potresti non volere o di cui hai bisogno, o che potrebbe non essere sul sistema di destinazione, ad esempio OS X
Jason S

6
@JasonS GNU Coreutils è troppo buono per non essere usato. Include molte ottime utilità. È così buono in effetti che il kernel Linux è inutile senza di esso e perché alcune persone lo chiamano GNU / Linux. Coreutils è fantastico. risposta eccellente questa dovrebbe essere la risposta selezionata.

7
@omouse La domanda menziona specificamente "una soluzione pronta all'uso" (per OS X). Indipendentemente da quanto sia fantastico coreutils, non è "pronto all'uso" su OS X, quindi la risposta non è buona. Non è questione di quanto sia buono coreutils o se sia migliore di quello che c'è su OS X. Ci sono soluzioni "pronte all'uso" che $( cd "$(dirname "$0")" ; pwd -P )funzionano bene per me.
Jason S

2
Una delle "caratteristiche" di OS X è che la directory ei nomi dei file non fanno distinzione tra maiuscole e minuscole. Di conseguenza, se si dispone di una directory chiamata XXXe qualcuno cd xxxpoi pwdtornerà .../xxx. Ad eccezione di questa risposta, tutte le soluzioni di cui sopra tornano xxxquando ciò che vuoi veramente è XXX. Grazie!
Andrew

1
Non so come copiare, incollare e reinventare il codice all'infinito sia meglio di una soluzione di gestore di pacchetti esistente. Se ne avevi bisogno realpath, cosa succede quando quasi sicuramente avrai bisogno di altri articoli coreutils? Riscrivere anche quelle funzioni in bash? : P
Ezekiel Victor

28

Uffa. Ho trovato le risposte precedenti un po 'carenti per alcuni motivi: in particolare, non risolvono più livelli di collegamenti simbolici, e sono estremamente "Bash-y". Sebbene la domanda originale chieda esplicitamente uno "script Bash", fa anche riferimento a BSD di Mac OS X, non GNU readlink. Quindi ecco un tentativo di una ragionevole portabilità (l'ho controllato con bash come 'sh' e trattino), risolvendo un numero arbitrario di collegamenti simbolici; e dovrebbe funzionare anche con gli spazi nei percorsi, anche se non sono sicuro del comportamento se c'è uno spazio bianco nel nome di base dell'utilità stessa, quindi forse, um, evitarlo?

#!/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 "$@"

Spero che possa essere utile a qualcuno.


1
Suggerirei solo di utilizzare localper le variabili definite all'interno della funzione per non inquinare lo spazio dei nomi globale. Ad esempio local OURPWD=.... Funziona almeno per bash.
Michael Paesold

Inoltre, il codice non deve utilizzare le maiuscole per le variabili private. Le variabili maiuscole sono riservate per l'uso del sistema.
tripleee

Grazie per la sceneggiatura. Nel caso in cui i link e il file reale abbiano nomi di base diversi, sarebbe probabilmente una buona idea aggiungere un BASENAME=$(basename "$LINK") interno al mentre e usarlo nel secondo setter LINK e nel setter
REALPATH

Questo non gestisce collegamenti simbolici e ..riferimenti genitore come realpathfarebbe. Con homebrew coreutilsinstallato, prova ln -s /var/log /tmp/linkexamplequindi realpath /tmp/linkexample/../; questo stampa /private/var. Ma la tua funzione produce /tmp/linkexample/..invece, perché ..non è un collegamento simbolico.
Martijn Pieters

10

Una variante più adatta alla riga di comando della soluzione Python:

python -c "import os; print(os.path.realpath('$1'))"

2
Nel caso qualcuno fosse abbastanza pazzo da avviare un interprete Python per un singolo comando ...
Bachsau

Ero abbastanza arrabbiato, ma ho usatopython -c "import os; import sys; print(os.path.realpath(sys.argv[1]))"
Alex Chamberlain il

7

Stavo cercando una soluzione da utilizzare in uno script di provisioning del sistema, ovvero eseguire prima dell'installazione di Homebrew. In mancanza di una soluzione adeguata, scaricarei l'attività in un linguaggio multipiattaforma, ad esempio Perl:

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

Più spesso ciò che vogliamo effettivamente è la directory contenente:

here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")

Grande! Perl è carino per le piccole spese generali! Probabilmente potresti semplificare la prima versione in FULLPATH=$(perl -e "use Cwd 'abs_path'; print abs_path('$0')"). Qualche motivo contro?
F Pereira

@FPereira La generazione del codice del programma da una stringa fornita dall'utente senza caratteri di escape non è mai una buona idea. ''non è a prova di proiettile. Si interrompe se $0contiene una singola virgoletta, ad esempio. Un esempio molto semplice: prova la tua versione in /tmp/'/test.she chiama /tmp/'/test.shper il suo percorso completo.
4ae1e1

O ancora più semplice, /tmp/'.sh.
4ae1e1

6

Poiché esiste un percorso reale come altri hanno sottolineato:

// 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)

Quindi compilare makee inserire un collegamento software con:
ln -s $(pwd)/realpath /usr/local/bin/realpath


potremmo semplicemente fare gcc realpath.c -o /usr/local/bin/realpath?
Alexander Mills

1
@AlexanderMills Non dovresti eseguire il compilatore come root; e poi se non lo fai, non avrai i privilegi di scrivere a/usr/local/bin
tripleee

6

Usa Python per ottenerlo:

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

print(os.path.realpath(sys.argv[1]))

2
abs_path () {    
   echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
}

dirnamedarà il nome della directory /path/to/file, ad es /path/to.

cd /path/to; pwd assicura che il percorso sia assoluto.

basenamedarà solo il nome del file in /path/to/file, ie file.


Sebbene questo codice possa rispondere alla domanda, fornire un contesto aggiuntivo sul perché e / o come questo codice risponde alla domanda ne migliora il valore a lungo termine.
Igor F.

1

Quindi, come puoi vedere sopra, ci ho provato circa 6 mesi fa. Me ne sono completamente dimenticato finché non mi sono ritrovato ad aver bisogno di una cosa simile. Sono rimasto completamente scioccato nel vedere quanto fosse rudimentale; Ho insegnato a me stesso a programmare in modo piuttosto intensivo da circa un anno, ma spesso mi sento come se forse non avessi imparato nulla quando le cose vanno al peggio.

Vorrei rimuovere la "soluzione" sopra, ma mi piace davvero che sia una sorta di registrazione di quanto ho davvero imparato negli ultimi mesi.

Ma sto divagando. Mi sono seduto e ho capito tutto ieri sera. La spiegazione nei commenti dovrebbe essere sufficiente. Se vuoi tenere traccia della copia su cui sto continuando a lavorare, puoi seguire questa sintesi. Questo probabilmente fa quello che ti serve.

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

1
Il collegamento essenziale sembra interrotto.
Alex

questa risposta funziona: stackoverflow.com/a/46772980/1223975 .... dovresti semplicemente usarlo.
Alexander Mills

Nota che la tua implementazione non risolve i collegamenti simbolici prima dei ..riferimenti genitore; ad esempio /foo/link_to_other_directory/..è risolto come /foopiuttosto che come genitore di qualunque percorso il collegamento simbolico /foo/link_to_other_directorypunta. readlink -fe realpathrisolvere ogni componente del percorso partendo dalla radice e aggiornando le destinazioni del collegamento anteposto al resto ancora in fase di elaborazione. Ho aggiunto una risposta a questa domanda reimplementando quella logica.
Martijn Pieters

1

realpath per Mac OS X

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

Esempio con percorso correlato:

realpath "../scripts/test.sh"

Esempio con cartella home

realpath "~/Test/../Test/scripts/test.sh"

1
bella soluzione semplice, ho appena trovato un avvertimento, quando invocarlo con ..esso non produce la risposta giusta, quindi ho aggiunto un controllo se il percorso specificato è una directory: if test -d $path ; then echo $(cd "$path"; pwd) ; else [...]
herbert

Non ha funzionato per me, mentre "$(dirname $(dirname $(realpath $0)))"funziona, quindi ho bisogno di qualcos'altro ...
Alexander Mills

L' uso inutile diecho non deve essere perpetrato.
tripleee

Questo in realtà non risolve i collegamenti simbolici proprio come farebbe realpath. Risolve i ..riferimenti padre prima di risolvere i collegamenti simbolici, non dopo; provalo con homebrew coreutilsinstallato, crea un collegamento con ln -s /var/log /tmp/linkexamplequindi esegui realpath /tmp/linkexample/../; questo stampa /private/var. Ma la tua funzione produce /tmp/linkexample/..invece, perché pwdmostra ancora /tmp/linkexampledopo il cd.
Martijn Pieters

1

Su macOS, l'unica soluzione che ho trovato per gestire in modo affidabile i collegamenti simbolici è usare realpath. Poiché ciò richiede brew install coreutils, ho solo automatizzato quel passaggio. La mia implementazione è simile a questa:

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


Questo errore se non è stato brewinstallato, ma in alternativa è possibile installare anche quello. Semplicemente non mi sentivo a mio agio ad automatizzare qualcosa che arriccia il codice rubino arbitrario dalla rete.

Nota che questa è una variazione automatica della risposta di Oleg Mikheev .


Un test importante

Un buon test di una qualsiasi di queste soluzioni è:

  1. mettere il codice in un file di script da qualche parte
  2. in un'altra directory, symlink ( ln -s) a quel file
  3. eseguire lo script da quel collegamento simbolico

La soluzione dereferenzia il collegamento simbolico e ti fornisce la directory originale? Se è così, funziona.


0

Questo sembra funzionare per OSX, non richiede alcun binario ed è stato estratto da qui

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

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

0

Mi piace questo:

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

0

Avevo bisogno di una realpathsostituzione su OS X, che funzionasse correttamente su percorsi con collegamenti simbolici e riferimenti padre proprio come readlink -ffarebbe . Ciò include la risoluzione dei collegamenti simbolici nel percorso prima della risoluzione dei riferimenti padre; ad esempio, se hai installato la coreutilsbottiglia homebrew , esegui:

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

Nota che readlink -fè stato risolto /tmp/linkeddir prima di risolvere il ..riferimento alla directory principale. Naturalmente, non v'è readlink -fsu Mac sia .

Quindi, come parte dell'implementazione di a bash per realpathho re-implementato ciò che fa una canonicalize_filename_mode(path, CAN_ALL_BUT_LAST)chiamata di funzione GNUlib , in Bash 3.2; questa è anche la chiamata di funzione che GNU readlink -ffa:

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

Include il rilevamento del collegamento simbolico circolare, che esce se lo stesso percorso (intermedio) viene visto due volte.

Se tutto ciò di cui hai bisogno è readlink -f, puoi usare quanto sopra come:

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
}

Perché realpath, avevo anche bisogno di --relative-toe --relative-basesupporto, che ti danno percorsi relativi dopo la canonicalizzazione:

_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

Ho incluso unit test nella mia richiesta di revisione del codice per questo codice .


-2

Sulla base della comunicazione con il commentatore, ho convenuto che è molto difficile e non ha un modo banale per implementare un realpath che si comporta totalmente come Ubuntu.

Ma la seguente versione, può gestire i casi d'angolo, la migliore risposta non può e soddisfare le mie esigenze quotidiane su MacBook. Metti questo codice nel tuo ~ / .bashrc e ricorda:

  • arg può essere solo 1 file o directory, nessun carattere jolly
  • nessuno spazio nella directory o nel nome del file
  • almeno il file o la directory principale della directory esiste
  • sentiti libero di usare. .. / cosa, questi sono al sicuro

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

Vuoi evitare l' uso inutile diecho . Fa semplicemente pwdla stessa cosa echo $(pwd)senza dover generare una seconda copia della shell. Inoltre, non citare l'argomento echoè un bug (perderai qualsiasi spazio bianco iniziale o finale, qualsiasi carattere di spazio vuoto interno adiacente e i caratteri jolly saranno espansi, ecc.). Vedi ancora stackoverflow.com/questions/10067266/...
tripleee

Inoltre, il comportamento per i percorsi inesistenti è bacato; ma immagino che sia quello che la frase "ma ricorda" forse sta cercando di dire. Sebbene il comportamento su Ubuntu non sia certamente quello di stampare la directory corrente quando si richiede realpathdi una directory inesistente.
tripleee

Per coerenza, probabilmente preferire dir=$(dirname "$1"); file=$(basename "$1")invece la sintassi del backtick obsoleta. Notare anche la corretta citazione degli argomenti, ancora una volta.
tripleee

La tua risposta aggiornata sembra non riuscire a correggere molti dei bug e ne aggiunge di nuovi.
tripleee

per favore dammi alcuni casi specifici non riusciti, perché tutti i test che eseguo nel desktop ubuntu 18.04 sono ok, grazie.
occia
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.