ChatGPT解决这个技术问题 Extra ChatGPT

Get current directory name (without full path) in a Bash script

How would I get just the current working directory name in a bash script, or even better, just a terminal command.

pwd gives the full path of the current working directory, e.g. /opt/local/bin but I only want bin

Just got the 1000th upvote :)

C
Charles Duffy

No need for basename, and especially no need for a subshell running pwd (which adds an extra, and expensive, fork operation); the shell can do this internally using parameter expansion:

result=${PWD##*/}          # to assign to a variable
result=${result:-/}        # to correct for the case where PWD=/

printf '%s\n' "${PWD##*/}" # to print to stdout
                           # ...more robust than echo for unusual names
                           #    (consider a directory named -e or -n)

printf '%q\n' "${PWD##*/}" # to print to stdout, quoted for use as shell input
                           # ...useful to make hidden characters readable.

Note that if you're applying this technique in other circumstances (not PWD, but some other variable holding a directory name), you might need to trim any trailing slashes. The below uses bash's extglob support to work even with multiple trailing slashes:

dirname=/path/to/somewhere//
shopt -s extglob           # enable +(...) glob syntax
result=${dirname%%+(/)}    # trim however many trailing slashes exist
result=${result##*/}       # remove everything before the last / that still remains
result=${result:-/}        # correct for dirname=/ case
printf '%s\n' "$result"

Alternatively, without extglob:

dirname="/path/to/somewhere//"
result="${dirname%"${dirname##*[!/]}"}" # extglob-free multi-trailing-/ trim
result="${result##*/}"                  # remove everything before the last /
result=${result:-/}                     # correct for dirname=/ case

What is the difference between ${PWD##*/} and $PWD?
@Mr_Chimp the former is a parameter expansion operations which trims the rest of the directory to provide only the basename. I have a link in my answer to the documentation on parameter expansion; feel free to follow that if you have any questions.
@stefgosselin $PWD is the name of a built-in bash variable; all built-in variable names are in all-caps (to distinguish them from local variables, which should always have at least one lower-case character). result=${PWD#*/} does not evaluate to /full/path/to/directory; instead, it strips only the first element, making it path/to/directory; using two # characters makes the pattern match greedy, matching as many characters as it can. Read the parameter expansion page, linked in the answer, for more.
Note that the output from pwd is not always the same as the value of $PWD, due to complications arising from cding through symbolic links, whether bash has the -o physical option set, and so on. This used to get especially nasty around handling of automounted directories, where recording the physical path instead of the logical one would produce a path that, if used, would allow the automounter to spontaneously dismount the directory one was using.
Nice! Someone should write a book for old Borne shell guys like me. Maybe there is one out there? It could have a crotchity old sys admin saying stuff like, "back in my day we only sh and csh and if you wanted the backspace key to work you had to read the whole stty man page, and we liked it!"
A
Arkady

Use the basename program. For your case:

% basename "$PWD"
bin

Isn't this the purpose of the basename program? What is wrong with this answer besides missing the quotes?
@Nacht basename is indeed an external program that does the right thing, but running any external program for a thing bash can do out-of-the-box using only built-in functionality is silly, incurring performance impact (fork(), execve(), wait(), etc) for no reason.
...as an aside, my objections to basename here don't apply to dirname, because dirname has functionality that ${PWD##*/} does not -- transforming a string with no slashes to ., for instance. Thus, while using dirname as an external tool has performance overhead, it also has functionality that helps to compensate for same.
Sure using basename is less "efficient". But it's probably more efficient in terms of developer productivity because it's easy to remember compared to the ugly bash syntax. So if you remember it, go for it. Otherwise, basename works just as well. Has anyone ever had to improve the "performance" of a bash script and make it more efficient?
@usr, ...speaking as someone who's written bash scripts that operate over maildirs (a mailbox storage format with one file per email, as used by qmail), yes, absolutely, I've had real-world cause to pay attention to efficiency in scripting. We're talking on the scale of 20ms per command substitution on non-embedded hardware -- loop over 100,000 items and you're talking real time even if your code just has one of them. (Sure, there's a point where shell is no longer the right tool, but you reach that point far faster if you don't pay any attention to how well you write your code).
g
gniourf_gniourf
$ echo "${PWD##*/}"


@jocap ...except that the usage of echo there, without quoting the argument, is wrong. If the directory name has multiple spaces, or a tab character, it'll be collapsed to a single space; if it has a wildcard character, it will be expanded. Correct usage would be echo "${PWD##*/}".
@jocap ...also, I'm not sure that "echo" is in fact a legitimate part of the answer. The question was how to get the answer, not how to print the answer; if the goal was to assign it to a variable, for instance, name=${PWD##*/} would be right, and name=$(echo "${PWD##*/}") would be wrong (in a needless-inefficiency sense).
A
Arsen Khachaturyan

Use:

basename "$PWD"

OR

IFS=/ 
var=($PWD)
echo ${var[-1]} 

Turn the Internal Filename Separator (IFS) back to space.

IFS= 

There is one space after the IFS.


g
gniourf_gniourf

You can use a combination of pwd and basename. E.g.

#!/bin/bash

CURRENT=`pwd`
BASENAME=`basename "$CURRENT"`

echo "$BASENAME"

exit;

Please, no. The backticks create a subshell (thus, a fork operation) -- and in this case, you're using them twice! [As an additional, albeit stylistic quibble -- variable names should be lower-case unless you're exporting them to the environment or they're a special, reserved case].
and as a second style quibble backtics should be replaced by $(). still forks but more readable and with less excaping needed.
By convention, environment variables (PATH, EDITOR, SHELL, ...) and internal shell variables (BASH_VERSION, RANDOM, ...) are fully capitalized. All other variable names should be lowercase. Since variable names are case-sensitive, this convention avoids accidentally overriding environmental and internal variables.
Depends on the convention. One could argue that all shell variables are environment variables (depending on the shell), and thus all shell variables should be in all-caps. Someone else could argue based on scope - global variables are all-caps, while local variables are lowercase, and these are all globals. Yet another might argue that making variables all-uppercase adds an extra layer of contrast with the commands littering shell scripts, which are also all lower-case short but meaningful words. I may or may not /agree/ with any of those arguments, but I've seen all three in use. :)
O
Orange

How about grep:

pwd | grep -o '[^/]*$'

would not recommend because it seems to get some color information whereas pwd | xargs basename doesn't.. probably not that important but the other answer is simpler and more consistent across environments
u
user3278975
basename $(pwd)

or

echo "$(basename $(pwd))"

Needs more quotes to be correct -- as it is, the output of pwd is string-split and glob-expanded before being passed to basename.
Thus, basename "$(pwd)" -- though that's very inefficient compared to just basename "$PWD", which is itself inefficient compared to using a parameter expansion instead of calling basename at all.
r
rodvlopes

This thread is great! Here is one more flavor:

pwd | awk -F / '{print $NF}'

F
FDS

I like the selected answer (Charles Duffy), but be careful if you are in a symlinked dir and you want the name of the target dir. Unfortunately I don't think it can be done in a single parameter expansion expression, perhaps I'm mistaken. This should work:

target_PWD=$(readlink -f .)
echo ${target_PWD##*/}

To see this, an experiment:

cd foo
ln -s . bar
echo ${PWD##*/}

reports "bar"

DIRNAME

To show the leading directories of a path (without incurring a fork-exec of /usr/bin/dirname):

echo ${target_PWD%/*}

This will e.g. transform foo/bar/baz -> foo/bar


Unfortunately, readlink -f is a GNU extension, and thus not available on the BSDs (including OS X).
a
anomal
echo "$PWD" | sed 's!.*/!!'

If you are using Bourne shell or ${PWD##*/} is not available.


FYI, ${PWD##*/} is POSIX sh -- every modern /bin/sh (including dash, ash, etc) supports it; to hit actual Bourne on a recent box, you'd need to be on a mildly oldish Solaris system. Beyond that -- echo "$PWD"; leaving out the quotes leads to bugs (if the directory name has spaces, wildcard characters, etc).
Yes, I was using an oldish Solaris system. I have updated the command to use quotes.
A
Arton Dorneles

Surprisingly, no one mentioned this alternative that uses only built-in bash commands:

i="$IFS";IFS='/';set -f;p=($PWD);set +f;IFS="$i";echo "${p[-1]}"

As an added bonus you can easily obtain the name of the parent directory with:

[ "${#p[@]}" -gt 1 ] && echo "${p[-2]}"

These will work on Bash 4.3-alpha or newer.


surprisingly ? just joking, but others are much shorter and easier to remember
This is pretty funny =P Had not heard of $IFS (internal field separator) before this. my bash was too old, but can test it here: jdoodle.com/test-bash-shell-script-online
g
geckos

There are a lots way of doing that I particularly liked Charles way because it avoid a new process, but before know this I solved it with awk

pwd | awk -F/ '{print $NF}'

u
user2208522

For the find jockeys out there like me:

find $PWD -maxdepth 0 -printf "%f\n"

Change the $PWD to "$PWD" to correctly handle unusual directory names.
D
Dutch Glory

i usually use this in sh scripts

SCRIPTSRC=`readlink -f "$0" || echo "$0"`
RUN_PATH=`dirname "${SCRIPTSRC}" || echo .`
echo "Running from ${RUN_PATH}"
...
cd ${RUN_PATH}/subfolder

you can use this to automate things ...


readlink: illegal option -- f usage: readlink [-n] [file ...]
f
fedorqui

Just use:

pwd | xargs basename

or

basename "`pwd`"

This is in all respects a worse version of the answer previously given by Arkady (stackoverflow.com/a/1371267/14122): The xargs formultion is inefficient and buggy when directory names contain literal newlines or quote characters, and the second formulation calls the pwd command in a subshell, rather than retrieving the same result via a built-in variable expansion.
@CharlesDuffy Nevertheless is it a valid and practical answer to the question.
@bachph, I disagree: A buggy answer (f/e, an answer that doesn't work when directory names contain spaces) should not be considered an answer at all.
A
Abhishek Gurjar

Below grep with regex is also working,

>pwd | grep -o "\w*-*$"

It doesn't work if directory name contains "-" characters.
G
Gautam Sreekumar

If you want to see only the current directory in the bash prompt region, you can edit .bashrc file in ~. Change \w to \W in the line:

PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '

Run source ~/.bashrc and it will only display the directory name in the prompt region.

Ref: https://superuser.com/questions/60555/show-only-current-directory-name-not-full-path-on-bash-prompt


S
Steve Benner

I strongly prefer using gbasename, which is part of GNU coreutils.


FYI, it doesn't come with my Ubuntu distribution.
@KatasticVoyage, it's there on Ubuntu, it's just called basename there. It's only typically called gbasename on MacOS and other platforms that otherwise ship with a non-GNU basename.
n
niks_4143

You can use the basename utility which deletes any prefix ending in / and the suffix (if present in string) from string, and prints the result on the standard output.

$basename <path-of-directory>

o
octopusgrabbus

The following commands will result in printing your current working directory in a bash script.

pushd .
CURRENT_DIR="`cd $1; pwd`"
popd
echo $CURRENT_DIR

(1) I'm not sure where we're ensuring that the argument list is such that $1 will contain the current working directory. (2) The pushd and popd serve no purpose here because anything inside backticks is done in a subshell -- so it can't affect the parent shell's directory to start with. (3) Using "$(cd "$1"; pwd)" would be both more readable and resilient against directory names with whitespace.
B
Benjamin Zach

Just remove any character until a / (or \, if you're on Windows). As the match is gonna be made greedy it will remove everything until the last /:

pwd | sed 's/.*\///g'

In your case the result is as expected:

λ a='/opt/local/bin'

λ echo $a | sed 's/.*\///g'
bin