35

I want to get just the name of the parent directory for a file.

Example: When I have path=/a/b/c/d/file, I want only d and not /a/b/c/d (which I get from dirname $path) as output.

Is there any sophisticated way to do this?

37

It sounds like you want the basename of the dirname:

$ filepath=/a/b/c/d/file
$ parentname="$(basename "$(dirname "$filepath")")"
$ echo "$parentname"
d
4
  • why am I receiving . instead of the parent directory on debian?
    – Amir
    Nov 10 '15 at 10:17
  • @Amir: Are you starting with a full path, or just a filename? If it's just a filename, the dirname command will assume it's in the current directory (aka "."). Nov 10 '15 at 15:09
  • well, I am using this: parentname="$(basename "$(dirname "$pwd")")"
    – Amir
    Nov 10 '15 at 15:33
  • 1
    @Amir: Shell variables are case sensitive, and PWD must be capitalized. Try parentname="$(basename "$(dirname "$PWD")")". Nov 10 '15 at 17:31
7

You can use pwd to get the current working directory, and use parameter expansion to avoid forking it into another (sub)shell.

echo ${PWD##*/}

Edit: proven source

1
  • The question has nothing to do with the current directory. You could use ${path##*/}
    – Matteo
    Jan 20 '13 at 9:22
3

I think this is a less-resource solution:

 $ filepath=/a/b/c/d/file
 $ echo ${${filepath%/*}##*/}
 d

edit: Sorry, nested expansion isn't possible in bash, but it works in zsh. Bash-version:

 $ filepath=/a/b/c/d/file
 $ path=${filepath%/*}
 $ echo ${path##*/}
 d
2
  • There are some edge cases this doesn't handle well, mainly when there isn't a full multilevel path. For example, try it with filepath=file or filepath=/file`. Jan 20 '13 at 22:31
  • Indeed. But what is the parent directory of foofile? If it isn't full path can't know (maybe if foofile is an existing file not only a "string").
    – uzsolt
    Jan 21 '13 at 8:54
2

In bash, in one line:

$ dirname /a/b/c/d/file | sed 's,^\(.*/\)\?\([^/]*\),\2,'
4
  • 3
    can you please elaborate the procedures involved? it can be of help to future readers. also, please try not to write 1/2 line answers. Apr 28 '13 at 19:18
  • dirname gets the directory in which file supposedly lives ( /a/b/c/d ). sed is used with its search/replace function as a "finder". , is the separator, the pattern we search for is ^\(.*/\)\?\([^/]*\) : ^ is the start of the string. Escaped parentheses surround the first capture group as \(.*/\) - a greedy search up to a / character, can include other /s with the 2nd capture group determining which /. Note that the use of , as a separator allows us to have the unescaped /. \? means zero or one of the first capture group. (useful, e.g. if d/file were passed in).... Jun 13 '20 at 15:33
  • The second capture group \([^/]*\) catches zero or more non-/ characters. This is useful if no parent were actually passed in, e.g. file. I would actually put a $ for the end of the string after that last parentheses, but what's there works. So, our search pattern was between the first and second , characters. In between the second and third (last) , characters we have the replacement, \2 meaning the second capturing group (second part inside escaped parentheses), i.e. everything after the last / from the result of dirname. Jun 13 '20 at 15:49
  • So, in the example given: result of dirname is /a/b/c/d. \(.*/\)\? gives /a/b/c/ (as \1, incidentally), which isn't included in the sed replacement - i.e. it gets cut away. \([^/]*\) captures d as \2. The replacement is \2, so the output is d. Jun 13 '20 at 15:53
1

I like Julian67's answer above best, but here is a bit an expanded version:

file_path = "a/b/c/d/file.txt"
parent=$(echo $file_path | sed -e 's;\/[^/]*$;;') # cut away "/file.txt";'$' is end of string
parent=$(echo $parent | sed -e 's;.*\/;;')  # cut away "/a/b/c/"
echo $parent # --> you get "d"
1
  • In case the script becomes a multiline solution, how can it be aliased to a command for cmder (where should one store it? what type of file?)
    – Artur
    Nov 28 '20 at 18:40
1

dirname and basename should be used for this task.

The short answer is

dirname /a/b/c/d/file | xargs basename
=> d

The first step, getting the dirname of the path, yields the path of the parent folder, as seen below.

dirname /a/b/c/d/file
=> /a/b/c/d

The path of the parent folder is then piped to another command with |. Since basename (and dirname) don't run on streams and instead just on parametized inputs, you cannot directly pipe the results to basename with | basename. This however is fixed by xargs, which turns the stream from the pipe into an input for basename

This allows the final solution to become

dirname /a/b/c/d/file | xargs basename

Which could also be written as ...

echo '/a/b/c/d/file' | xargs dirname | xargs basename

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.