3

I need the basename of a file that is given as an argument to a bash script. The basename should be stripped of its file extension. Let's assume $1 = "/somefolder/andanotherfolder/myfile.txt", the desired output would be "myfile".

The current attempt creates an intermediate variable that I would like to avoid:

BASE=$(basename "$1")
NOEXT="${BASE%.*}"

My attempt to make this a one-liner would be piping the output of basename. However, I do not know how to pipe stdout to a string substitution.

EDIT: this needs to work for multiple file extensions with possibly differing lengths, hence the string substitution attempt as given above.

stee
  • 101
  • 7

4 Answers4

5

Why not Zoidberg ? Ehhmm.. I meant why not remove the ext before going for basename ?

basename "${1%.*}"

Unless of course you have directory paths with dots, then you'll have to use basename before and remove the extension later:

echo $(basename "$1") | awk 'BEGIN { FS = "." }; { print $1 }'

The awk solution will remove anything after the first dot from the filename.

There's a regular expression based solution which uses sed to remove only the extension after last dot if it exists:

echo $(basename "$1") | sed 's/\(.*\)\..*/\1/'

This could even be improved if you're sure that you've got alphanumeric extensions of 3-4 characters (eg: mp3, mpeg, jpg, txt, json...)

echo $(basename "$1") | sed 's/\(.*\)\.[[:alnum:]]\{3\}$/\1/'
Orsiris de Jong
  • 2,819
  • 1
  • 26
  • 48
  • I really like the first option for the sake of simplicity and readability. Paths including dots could be an issue though. – stee Jul 01 '20 at 10:41
  • Actually, I just tried this with a path containing foldernames using dots and it worked out. – stee Jul 01 '20 at 10:44
  • Indeed it will work even with paths that contains dots, but only if your filename has contains a dot too. if $1= '/some.dir/myfile' it would cut at the first trailing dot it finds. The second version is just more safe to use, even if it's uglier ;) But bash isn't the most beautiful script lang anyway IMO ;) – Orsiris de Jong Jul 01 '20 at 10:50
  • You're right. On the flipside of the coin: The awk solution does not work properly if the filename itself contains a dot before the file ending. – stee Jul 01 '20 at 11:37
  • So after some more thought it seems like the cleanest, yet ugly solution may be this: `echo $(basename "$1") | rev | cut -d"." -f 2- | rev` It will get the basename and remove everything after the last `.` of the basename. – stee Jul 01 '20 at 11:39
  • That's a lot of uglyness indeed. I added a sed based solution which uses regular expressions to restrict to file extensions. – Orsiris de Jong Jul 02 '20 at 12:47
3

How about this?

NEXT="$(basename -- "${1%.*}")"

Testing:

set -- '/somefolder/andanotherfolder/myfile.txt'
NEXT="$(basename -- "${1%.*}")"
echo "$NEXT"

myfile

Alternatively:

set -- "${1%.*}"; NEXT="${1##*/}"
Léa Gris
  • 17,497
  • 4
  • 32
  • 41
2
NOEXT="${1##*/}"; NOEXT="${NOEXT%.*}"
alecxs
  • 701
  • 8
  • 17
1

How about:

$ [[ $var =~ [^/]*$ ]] && echo ${BASH_REMATCH%.*}
myfile
James Brown
  • 36,089
  • 7
  • 43
  • 59