Hoe kan ik de brondirectory van een Bash-script uit het script zelf halen?

Hoe kom ik aan het pad van de map waarin een Bash-script bevindt zich, indat script?

Ik wil een Bash-script gebruiken als opstartprogramma voor een andere toepassing. Ik wil de werkdirectory wijzigen in die waar het Bash-script zich bevindt, zodat ik de bestanden in die directory kan bewerken, zoals:

$ ./application

Antwoord 1, autoriteit 100%

#!/usr/bin/env bash
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"

is een handige one-liner die je de volledige mapnaam van het script geeft, ongeacht waar het vandaan wordt aangeroepen.

Het werkt zolang het laatste onderdeel van het pad dat wordt gebruikt om het script te vinden geen symbolische link is (directorylinks zijn in orde). Als je ook links naar het script zelf wilt oplossen, heb je een oplossing met meerdere regels nodig:

#!/usr/bin/env bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"

Deze laatste werkt met elke combinatie van aliassen, source, bash -c, symlinks, enz.

Pas op:als u cdnaar een andere map gaat voordat u dit fragment uitvoert, kan het resultaat onjuist zijn!

Pas ook op voor $CDPATHgotchas, en stderr output-bijwerkingen als de gebruiker cd slim heeft overschreven om de output naar stderr om te leiden (inclusief escape-reeksen, zoals bij het aanroepen van update_terminal_cwd >&2op Mac). Door >/dev/null 2>&1toe te voegen aan het einde van uw cd-opdracht, worden beide mogelijkheden opgelost.

Probeer deze uitgebreidere vorm uit te voeren om te begrijpen hoe het werkt:

#!/usr/bin/env bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  TARGET="$(readlink "$SOURCE")"
  if [[ $TARGET == /* ]]; then
    echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
    SOURCE="$TARGET"
  else
    DIR="$( dirname "$SOURCE" )"
    echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
    SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
  fi
done
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"

En het zal iets afdrukken als:

SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'

Antwoord 2, autoriteit 14%

Gebruik dirname "$0":

#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"

Het gebruik van alleen pwdwerkt niet als u het script niet uitvoert vanuit de map waarin het zich bevindt.

[[email protected] ~]$ pwd
/home/matt
[[email protected] ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[[email protected] ~]$ cd /tmp
[[email protected] tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp

Antwoord 3, autoriteit 8%

De opdracht dirnameis de eenvoudigste, eenvoudig het pad naar de bestandsnaam ontleden van de variabele $0(scriptnaam):

dirname "$0"

Maar, zoals matt bal aangaf, het geretourneerde pad is afhankelijk van hoe het script wordt aangeroepen. pwddoet het werk niet omdat dat u alleen vertelt wat de huidige map is, niet in welke map het script zich bevindt. Bovendien, als een symbolische link naar een script wordt uitgevoerd, gaat u krijg een (waarschijnlijk relatief) pad naar waar de link zich bevindt, niet het eigenlijke script.

Enkele anderen hebben de opdracht readlinkgenoemd, maar op zijn eenvoudigst kunt u het volgende gebruiken:

dirname "$(readlink -f "$0")"

readlinkzal het scriptpad omzetten in een absoluut pad vanaf de root van het bestandssysteem. Dus alle paden die enkele of dubbele punten, tildes en/of symbolische links bevatten, worden omgezet in een volledig pad.

Hier is een script dat elk van deze demonstreert, whatdir.sh:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"

Dit script uitvoeren in mijn thuismap, met een relatief pad:

>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

Nogmaals, maar met het volledige pad naar het script:

>>>$ /Users/phatblat/whatdir.sh
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

Nu mappen wijzigen:

>>>$ cd /tmp
>>>$ ~/whatdir.sh
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

En tot slot een symbolische link gebruiken om het script uit te voeren:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat

Antwoord 4, autoriteit 3%

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
  while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`;
  SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

Het werkt voor alle versies, inclusief

  • wanneer gebeld via softlink met meerdere diepten,
  • wanneer het bestand het
  • wanneer script wordt aangeroepen door commando “source” oftewel .(punt) operator.
  • wanneer arg $0wordt gewijzigd van beller.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Als alternatief, als het Bash-script zelf een relatieve symlinkis, wil jehet volgen en het volledige pad van het gekoppelde script retourneren:

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
if ([ -h "${SCRIPT_PATH}" ]) then
  while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

SCRIPT_PATHwordt in het volledige pad gegeven, hoe het ook wordt genoemd.

Zorg ervoor dat u dit aan het begin van het script vindt.


Antwoord 5, autoriteit 2%

U kunt $BASH_SOURCEgebruiken:

#!/bin/bash
scriptdir=`dirname "$BASH_SOURCE"`

Houd er rekening mee dat je #!/bin/bashmoet gebruiken en niet #!/bin/shomdat het een Bash-extensie is.


Antwoord 6, autoriteit 2%

Kort antwoord:

`dirname $0`

of (bij voorkeur):

$(dirname "$0")

Antwoord 7

Dit zou het moeten doen:

DIR="$(dirname "$(readlink -f "$0")")"

Dit werkt met symbolische links en spaties in het pad.

Bekijk de man-pagina’s voor dirnameen readlink.

Vanaf het commentaarspoor lijkt het niet te werken met Mac OS.
Ik heb geen idee waarom dat is. Suggesties?


Antwoord 8

Hier is een gemakkelijk te onthouden script:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # Get the directory name
DIR="$(realpath "${DIR}")"    # Resolve its full path if need be

9

SCRIPT_DIR=$( cd ${0%/*} && pwd -P )

10

$(dirname "$(readlink -f "$BASH_SOURCE")")

11

Hiermee krijgt u de huidige werkdirectory op mac & nbsp; os & nbsp; x & nbsp; v10.6.6 ( Snow & Nbsp; Leopard):

DIR=$(cd "$(dirname "$0")"; pwd)

12

Dit is Linux-specifiek, maar u kunt gebruiken:

SELF=$(readlink /proc/$$/fd/255)

13

Hier is een POSIX-compatibele one-liner:

SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`
# test
echo $SCRIPT_PATH

14

De kortste en meest elegante manier om dit te doen is:

#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY

Dit zou op alle platforms werken en super schoon is.

Meer details zijn te vinden in “Welke map is dat bash-script in? “.


15

Ik heb ze allemaal geprobeerd en geen enkele werkte. Eentje was er heel dichtbij, maar het had een klein insect dat het ernstig brak; ze zijn vergeten het pad tussen aanhalingstekens te zetten.

Veel mensen gaan er ook van uit dat je het script vanuit een shell uitvoert, dus vergeten ze dat wanneer je een nieuw script opent, het standaard bij jou thuis staat.

Probeer deze map voor de grootte:

/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text

Dit doet het goed, ongeacht hoe of waar je het uitvoert:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"

Dus om het echt nuttig te maken, kun je als volgt naar de directory van het draaiende script gaan:

cd "`dirname "$0"`"

Antwoord 16

Dit is de eenvoudige, correcte manier:

actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")

Uitleg:

  • ${BASH_SOURCE[0]}– het volledige pad naar het script. De waarde hiervan zal correct zijn, zelfs wanneer het script wordt gesourced, b.v. source <(echo 'echo $0')drukt bashaf, terwijl het vervangen door ${BASH_SOURCE[0]}het volledige pad zal afdrukken van het schrift. (Dit gaat er natuurlijk van uit dat je in orde bent als je afhankelijk bent van Bash.)

  • readlink -f– Lost recursief alle symbolische links in het opgegeven pad op. Dit is een GNU-extensie en niet beschikbaar op (bijvoorbeeld) BSD-systemen. Als je een Mac gebruikt, kun je Homebrew gebruiken om GNU coreutilste installeren en dit te vervangen door greadlink -f.

  • En natuurlijk krijgt dirnamede bovenliggende directory van het pad.


Antwoord 17

Dit is een kleine herziening van de oplossing die e-satis en 3bcdnlklvc04a hebben aangegeven in hun antwoord:

SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
    SCRIPT_DIR="$PWD"
    popd > /dev/null
}

Dit zou nog steeds moeten werken in alle gevallen die ze hebben vermeld.

Dit voorkomt popdna een mislukte pushd. Met dank aan konsolebox.


Antwoord 18

#!/bin/sh
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`
done
scriptdir=`dirname "$PRG"`

Antwoord 19

Ik zou zoiets als dit gebruiken:

# Retrieve the full pathname of the called script
scriptPath=$(which $0)
# Check whether the path is a link or not
if [ -L $scriptPath ]; then
    # It is a link then retrieve the target path and get the directory name
    sourceDir=$(dirname $(readlink -f $scriptPath))
else
    # Otherwise just get the directory name of the script path
    sourceDir=$(dirname $scriptPath)
fi

Antwoord 20

Voor systemen met GNU coreutils readlink(bijvoorbeeld Linux):

$(readlink -f "$(dirname "$0")")

Het is niet nodig om BASH_SOURCEte gebruiken wanneer $0de scriptbestandsnaam bevat.


Antwoord 21

$_is het vermelden waard als alternatief voor $0. Als je een script uitvoert vanuit Bash, kan het geaccepteerde antwoord worden ingekort tot:

DIR="$( dirname "$_" )"

Merk op dat dit het eerste statement in je script moet zijn.


Antwoord 22

Dit zijn korte manieren om scriptinformatie te krijgen:

Mappen en bestanden:

   Script: "/tmp/src dir/test.sh"
    Calling folder: "/tmp/src dir/other"

Met deze commando’s:

   echo Script-Dir : `dirname "$(realpath $0)"`
    echo Script-Dir : $( cd ${0%/*} && pwd -P )
    echo Script-Dir : $(dirname "$(readlink -f "$0")")
    echo
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo
    echo Calling-Dir : `pwd`

En ik kreeg deze uitvoer:

    Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Name : test.sh
     Script-Name : test.sh
     Script-Dir-Relative : ..
     Script-Dir-Relative : ..
     Calling-Dir : /tmp/src dir/other

Zie ook: https://pastebin.com/j8kjxrpf


23

Dit werkt in Bash 3.2:

path="$( dirname "$( which "$0" )" )"

Als u een ~/binmap in uw $PATHhebt, hebt u Ain deze map. Het bronnen het script ~/bin/lib/B. U weet waar het meegeleverde script ten opzichte van de originele, in de libSubdirectory, maar niet waar het is ten opzichte van de huidige directory van de gebruiker.

Dit is opgelost door het volgende (binnenin A):

source "$( dirname "$( which "$0" )" )/lib/B"

Het maakt niet uit waar de gebruiker is of hoe hij / zij het script noemt. Dit zal altijd werken.


24

Probeer het gebruik van:

real=$(realpath $(dirname $0))

25

Ik heb veel van de gegeven antwoorden vergeleken en kwamen met wat meer compacte oplossingen. Deze lijken alle gekke randgevallen aan die ontstaan ​​uit je favoriete combinatie van:

  • absolute paden of relatieve paden
  • Bestand en map Soft Links
  • aanroepen als script, bash script, bash -c script, source scriptof . script
  • Spaties, tabbladen, newlines, Unicode, etc. in mappen en / of bestandsnaam
  • Bestandsnamen die beginnen met een koppelteken

Als je onder Linux draait, lijkt het erop dat het gebruik van de proc-handle de beste oplossing is om de volledig opgeloste bron van het momenteel lopende script te lokaliseren (in een interactieve sessie verwijst de link naar de respectievelijke /dev/pts/X):

resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"

Dit heeft een klein beetje lelijkheid, maar de oplossing is compact en gemakkelijk te begrijpen. We gebruiken niet alleen bash-primitieven, maar dat vind ik goed omdat readlinkvereenvoudigt de taak aanzienlijk. De echo Xvoegt een Xtoe aan het einde van de variabele tekenreeks, zodat eventuele spaties in de bestandsnaam niet worden opgegeten, en de parametervervanging ${VAR%X}aan het einde van de regel verwijdert de X. Omdat readlinkeen eigen nieuwe regel toevoegt (die normaal gesproken zou worden opgegeten in de opdrachtvervanging, zo niet voor onze vorige truc), moeten we daar ook vanaf. Dit wordt het gemakkelijkst bereikt met behulp van het $''aanhalingsschema, waarmee we escape-reeksen zoals \nkunnen gebruiken om nieuwe regels weer te geven (dit is ook hoe u gemakkelijk slinkse regels kunt maken benoemde mappen en bestanden).

Het bovenstaande moet uw behoeften dekken voor het lokaliseren van het momenteel lopende script op Linux, maar als u niet de procbestandssysteem tot uw beschikking hebt, of als u probeert de volledig opgelost te lokaliseren Pad van een ander bestand, dan vind je misschien de onderstaande code nuttig. Het is slechts een kleine modificatie van de bovenstaande one-liner. Als u rond speelt met vreemde map / bestandsnamen, controleert u de uitvoer met beide lsen readlinkinformatief, zoals ls, “Vereenvoudigd” “Paden, substitueren ?voor dingen zoals newlines.

absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}
ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"

26

Ik geloof dat ik deze heb. Ik ben laat aan het feest, maar ik denk dat sommigen zullen waarderen dat het hier is als ze deze draad tegenkomen. De opmerkingen moeten uitleggen:

#!/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: "[email protected]"\n" ;} # Borrow a horrible signal name.
# Probably best not to install as 'pathfull', if you can avoid it.
pathfull(){ cd "$(dirname "[email protected]")"; link="$(readlink "$(basename "[email protected]")")"
## 'test and 'ls' report different status for bad symlinks, so we use this.
 if [ ! -e "[email protected]" ]; then if $(ls -d "[email protected]" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "[email protected]" -a "$link" = "[email protected]" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "[email protected]" ] && [ ! -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 "[email protected]" | cut -c1)" = '/' ]; then
   printf "[email protected]\n"; exit 0; else printf "$(pwd)/$(basename "[email protected]")\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 "[email protected]"
fi

Antwoord 27

Probeer de volgende cross-compatibele oplossing:

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

Omdat commando’s zoals realpathof readlinkmogelijk niet beschikbaar zijn (afhankelijk van het besturingssysteem).

Opmerking: in Bash wordt aanbevolen om ${BASH_SOURCE[0]}te gebruiken in plaats van $0, anders kan het pad breken bij het sourcen van het bestand (source/.).

Als alternatief kunt u de volgende functie in Bash proberen:

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

Deze functie heeft één argument nodig. Als het argument al een absoluut pad heeft, druk het dan af zoals het is, druk anders de variabele $PWD+ bestandsnaamargument af (zonder het voorvoegsel ./).

Gerelateerd:

Other episodes