0

Having a pathname it is possible to extract its filename, excluding its apriori known extension, with basename:

$ pathname="/home/paulo/paulo.pdf"
$ printf "%s\n" "$(basename $pathname .pdf)"
paulo

But if the extension is not known how can this be done?

6
  • Does "not known" mean it may not have an extension? In zsh: $pathname:t:r
    – Kusalananda
    Commented Feb 10, 2020 at 12:57
  • @Kusalananda Yes, it may not have an extension. Commented Feb 10, 2020 at 12:59
  • 1
    If the filename is something like file.tar.gz, what would you consider to be the extension? Commented Feb 10, 2020 at 14:03
  • @glennjackman extension == '.tar.gz'. Commented Feb 10, 2020 at 14:04
  • 2
    It's not always as simple as "everything after the first dot". Some project use dots as something like a namespace. See github.com/asciidisco/Backbone.Marionette.Handlebars Commented Feb 10, 2020 at 14:24

3 Answers 3

6

In the zsh shell:

$ pathname=/home/paulo/paulo.pdf
$ printf '%s\n' $pathname:t:r
paulo
  • The :t modifier ("tail") extracts the last pathname component in $pathname (it works like basename).
  • The :r modifier ("root", I suppose) extracts the bit of the filename up to the extension, if there is one. The extension is the part of the filename that occurs after the last dot. This means that you would get an empty result for filenames like .zshrc.

The other related modifier are

  • :h ("head"), which works like dirname, and
  • :e ("extension"), which extracts the extension only.
1
  • Note that it's copied from csh. In csh, you'd also need :q to quote the result in case it contains SPC/TAB/NL or wildcards ($pathname:t:r:q). Commented Feb 10, 2020 at 15:23
5

You can use the shell’s parameter expansion modifiers:

$ pathname="/home/paulo/paulo.pdf"
$ filename=${pathname##*/}
$ printf "%s\n" "$filename"
paulo.pdf
$ basename=${filename%.*}
$ printf "%s\n" "$basename"
paulo

${pathname##*/} is expanded to the contents of pathname, minus the longest prefix matching */, i.e. the full path (if there is one). ${filename%.*} is expanded to the contents of filename, minus the shortest suffix matching .*, i.e. the file’s extension (if there is one).

Note that this only removes the last filename component introduced by a dot; so paulo.tar.gz would become paulo.tar, not paulo. Strictly speaking the extension is .gz (it’s a compressed file, which happens to be a tarball; the .tar extension only becomes really meaningful once the file has been extracted).

This also fails to work correctly for extension-less dot-files, e.g. .bashrc or .zshrc; basename ends up empty. Default values can be used to handle that:

$ pathname="/home/paulo/.zshrc"
$ filename=${pathname##*/}
$ printf "%s\n" "$filename"
.zshrc
$ basename=${filename%.*}
$ printf "%s\n" "$basename"

$ printf "%s\n" "${basename:-$filename}"
.zshrc
2
  • Note that it gives the empty string for /home/you/.zshrc for instance. Commented Feb 10, 2020 at 15:27
  • Ah yes, nice catch @Stéphane! Commented Feb 10, 2020 at 15:28
1

This should do what you want:

pathname="/home/paulo/paulo.pdf"
printf "%s\n" "$(basename $pathname)" | sed "s/\..*$//"
3
  • This would leave hello from hello.world.txt.gz.
    – Kusalananda
    Commented Feb 10, 2020 at 13:55
  • @Kusalananda This is true.. I will have to think some more. Commented Feb 10, 2020 at 19:04
  • It would still be a valid result, but only if you specify that the filename extension is everything after the first dot in the filename as this is slightly different from what's usually meant by a filename extension.
    – Kusalananda
    Commented Feb 10, 2020 at 19:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .