89

I need a short basename function (one-liner ?) for Javascript:

basename("/a/folder/file.a.ext") -> "file.a"
basename("/a/folder/file.ext") -> "file"
basename("/a/folder/file") -> "file"

That should strip the path and any extension.

Update: For dot at the beginning would be nice to treat as "special" files

basename("/a/folder/.file.a.ext") -> ".file.a"
basename("/a/folder/.file.ext") -> ".file"
basename("/a/folder/.file") -> ".file" # empty is Ok
basename("/a/folder/.fil") -> ".fil"  # empty is Ok
basename("/a/folder/.file..a..") -> # does'nt matter
3
  • What should basename('.foo') be? Commented Sep 29, 2010 at 11:14
  • 1
    @bobince - Empty string I believe. Commented Sep 29, 2010 at 12:27
  • I’ve just arrived at /^(?:.*\/(?=[^/]))?([^/]*)\/*$/.exec(pathname)[1] for a more Unixy or useful basename: any trailing slashes are ignored, empty string if input is empty or solely comprised of slashes, otherwise right-most non-slash word. Reviews welcome (please @ me)! Commented Apr 9, 2020 at 21:47

19 Answers 19

125
function basename(path) {
   return path.split('/').reverse()[0];
}

Breaks up the path into component directories and filename then returns the last piece (the filename) which is the last element of the array.

Sign up to request clarification or add additional context in comments.

5 Comments

path.split(/[\\/]/).pop(); - works with both separators, and no need to reverse
I think you should use this inside the function instead of variable name . so not select first class element only .
That awkward moment where the first comment has more votes than both the answer and the accepted answer 0_o +1 @3DFace
Does not work because it includes the file extension.
Just for completion .... function basename( _path = "" ) { return _path.includes( "/" ) ? _path.split( '/' ).reverse()[0] : _path; }
83
function baseName(str)
{
   var base = new String(str).substring(str.lastIndexOf('/') + 1); 
    if(base.lastIndexOf(".") != -1)       
        base = base.substring(0, base.lastIndexOf("."));
   return base;
}

If you can have both / and \ as separators, you have to change the code to add one more line

2 Comments

@Sibidharan can you elaborate?
Why is the new String(str) needed and not just str.substring(...)?
43

Any of the above works although they have no respect for speed/memory :-).

A faster/simpler implementation should uses as fewer functions/operations as possible. RegExp is a bad choice because it consumes a lot of resources when actually we can achieve the same result but easier.

An implementation when you want the filename including extension (which in fact this is the genuine definition of basename):

function basename(str, sep) {
    return str.substr(str.lastIndexOf(sep) + 1);
}

If you need a custom basename implementation that has to strip also the extension I would recommend instead a specific extension-stripping function for that case which you can call it whenever you like.

function strip_extension(str) {
    return str.substr(0,str.lastIndexOf('.'));
}

Usage example:

basename('file.txt','/'); // real basename
strip_extension(basename('file.txt','/')); // custom basename

They are separated such that you can combine them to obtain 3 different things: stripping the extention, getting the real-basename, getting your custom-basename. I regard it as a more elegant implementation than others approaches.

4 Comments

This is the most accurate and optimal answer. Thanks!
Speed is only important inside loops, especially innermost loops. Memory is only important when allocating many objects, especially large objects, at the same time. For a 'basename' function, which is not critical code, this answer is perfect.
If you have both scenarios with and without extension I altered strip_extension code: return str.substr(0, (str.lastIndexOf('.')==-1) ? str.length : str.lastIndexOf('.'));
substr is deprecated. Use slice in place.
24

Maybe try to use existing packages if you can. http://nodejs.org/api/path.html

var path = require('path');
var str = '/path/to/file/test.html'

var fileNameStringWithoutExtention = path.basename(str, '.html');
// returns 'test'

// let path determine the extension
var fileNameStringWithoutExtention = path.basename(str, path.extname(str));
// returns 'test'

Other examples:

var pathString = path.dirname(str);
var fileNameStringWithExtention = path.basename(str);
var fullPathAndFileNameString = path.join(pathString, fileNameString);

2 Comments

@Randy: Exactly what I needed! Under nodejs, no need to reinvent the wheel. (@Tazo, you are wrong about downloading. It's already there.)
@Randy I don't know why people uses custom function to get the filename instead of what you done
7
 basename = function(path) {
    return path.replace(/.*\/|\.[^.]*$/g, '');
 }

replace anything that ends with a slash .*\/ or dot - some non-dots - end \.[^.]*$ with nothing

Comments

4

Just like @3DFace has commented:

path.split(/[\\/]/).pop(); // works with both separators

Or if you like prototypes:

String.prototype.basename = function(sep) {
  sep = sep || '\\/';
  return this.split(new RegExp("["+sep+"]")).pop();
}

Testing:

var str = "http://stackoverflow.com/questions/3820381/need-a-basename-function-in-javascript";
alert(str.basename());

Will return "need-a-basename-function-in-javascript".

Enjoy!

Comments

4

Contrary to misinformation provided above, regular expressions are extremely efficient. The caveat is that, when possible, they should be in a position so that they are compiled exactly once in the life of the program.

Here is a solution that gives both dirname and basename.

const rx1 = /(.*)\/+([^/]*)$/;    // (dir/) (optional_file)
const rx2 = /()(.*)$/;            // ()     (file)

function dir_and_file(path) {
  // result is array with
  //   [0]  original string
  //   [1]  dirname
  //   [2]  filename
  return rx1.exec(path) || rx2.exec(path);
}
// Single purpose versions.
function dirname(path) {
  return (rx1.exec(path) || rx2.exec(path))[1];
}
function basename(path) {
  return (rx1.exec(path) || rx2.exec(path))[2];
}

As for performance, I have not measured it, but I expect this solution to be in the same range as the fastest of the others on this page, but this solution does more. Helping the real-world performance is the fact that rx1 will match most actual paths, so rx2 is rarely executed.

Here is some test code.

function show_dir(parts) {
  console.log("Original str :"+parts[0]);
  console.log("Directory nm :"+parts[1]);
  console.log("File nm      :"+parts[2]);
  console.log();
}
show_dir(dir_and_file('/absolute_dir/file.txt'));
show_dir(dir_and_file('relative_dir////file.txt'));
show_dir(dir_and_file('dir_no_file/'));
show_dir(dir_and_file('just_one_word'));
show_dir(dir_and_file('')); // empty string
show_dir(dir_and_file(null)); 

And here is what the test code yields:

# Original str :/absolute_dir/file.txt
# Directory nm :/absolute_dir
# File nm      :file.txt
# 
# Original str :relative_dir////file.txt
# Directory nm :relative_dir
# File nm      :file.txt
# 
# Original str :dir_no_file/
# Directory nm :dir_no_file
# File nm      :
# 
# Original str :just_one_word
# Directory nm :
# File nm      :just_one_word
# 
# Original str :
# Directory nm :
# File nm      :
# 
# Original str :null
# Directory nm :
# File nm      :null

By the way, "node" has a built in module called "path" that has "dirname" and "basename". Node's "path.dirname()" function accurately imitates the behavior of the "bash" shell's "dirname," but is that good? Here's what it does:

  1. Produces '.' (dot) when path=="" (empty string).
  2. Produces '.' (dot) when path=="just_one_word".
  3. Produces '.' (dot) when path=="dir_no_file/".

I prefer the results of the function defined above.

Comments

4

Using modern (2020) js code:

function basename (path) {
  return path.substring(path.lastIndexOf('/') + 1)
}
console.log(basename('/home/user/file.txt'))

Comments

2

Another good solution:

function basename (path, suffix) {
  //  discuss at: http://locutus.io/php/basename/
  // original by: Kevin van Zonneveld (http://kvz.io)
  // improved by: Ash Searle (http://hexmen.com/blog/)
  // improved by: Lincoln Ramsay
  // improved by: djmix
  // improved by: Dmitry Gorelenkov
  //   example 1: basename('/www/site/home.htm', '.htm')
  //   returns 1: 'home'
  //   example 2: basename('ecra.php?p=1')
  //   returns 2: 'ecra.php?p=1'
  //   example 3: basename('/some/path/')
  //   returns 3: 'path'
  //   example 4: basename('/some/path_ext.ext/','.ext')
  //   returns 4: 'path_ext'

  var b = path
  var lastChar = b.charAt(b.length - 1)

  if (lastChar === '/' || lastChar === '\\') {
    b = b.slice(0, -1)
  }

  b = b.replace(/^.*[\/\\]/g, '')

  if (typeof suffix === 'string' && b.substr(b.length - suffix.length) === suffix) {
    b = b.substr(0, b.length - suffix.length)
  }

  return b
}

from: http://locutus.io/php/filesystem/basename/

Comments

2

Defining a flexible basename implementation

Despite all the answers, I still had to produce my own solution which fits the following criteria:

  1. Is fully portable and works in any environment (thus Node's path.basename won't do)
  2. Works with both kinds of separators (/ and \)
  3. Allows for mixing separators - e.g. a/b\c (this is different from Node's implementation which respects the underlying system's separator instead)
  4. Does not return an empty path if path ends on separator (i.e. getBaseName('a/b/c/') === 'c')

Code

(make sure to open the console before running the Snippet)

/**
 * Flexible `basename` implementation
 * @see https://stackoverflow.com/a/59907288/2228771
 */
function getBasename(path) {
  // make sure the basename is not empty, if string ends with separator
  let end = path.length-1;
  while (path[end] === '/' || path[end] === '\\') {
    --end;
  }

  // support mixing of Win + Unix path separators
  const i1 = path.lastIndexOf('/', end);
  const i2 = path.lastIndexOf('\\', end);

  let start;
  if (i1 === -1) {
    if (i2 === -1) {
      // no separator in the whole thing
      return path;
    }
    start = i2;
  }
  else if (i2 === -1) {
    start = i1;
  }
  else {
    start = Math.max(i1, i2);
  }
  return path.substring(start+1, end+1);
}

// tests
console.table([
  ['a/b/c', 'c'],
  ['a/b/c//', 'c'],
  ['a\\b\\c', 'c'],
  ['a\\b\\c\\', 'c'],
  ['a\\b\\c/', 'c'],
  ['a/b/c\\', 'c'],
  ['c', 'c']
].map(([input, expected]) => {
  const result = getBasename(input);
  return {
    input, 
    result,
    expected,
    good: result === expected ? '✅' : '❌'
  };
}));

1 Comment

Excellent, this is what I needed to handle all browsers and platforms!!!
2

Fast without regular expressions, suitable for both path types '/' and '\'. Gets the extension also:

function baseName(str)
{
    let li = Math.max(str.lastIndexOf('/'), str.lastIndexOf('\\'));
    return new String(str).substring(li + 1);
}

1 Comment

Instead of the Math.max function why not use ||. This of course is susceptible to an invalid(?) string that contains / and \ characters. But if the full path is valid, then this shouldn't happen. Finally, it was asked before, but I didn't see that answer, why the new String vs just String?
1
function basename(url){
    return ((url=/(([^\/\\\.#\? ]+)(\.\w+)*)([?#].+)?$/.exec(url))!= null)? url[2]: '';
}

Comments

1

This is my implementation which I use in my fundamental js file.

// BASENAME

Window.basename = function() {
    var basename = window.location.pathname.split(/[\\/]/);
    return basename.pop() || basename.pop();
}

Comments

1

JavaScript Functions for basename and also dirname:

function basename(path) {
     return path.replace(/.*\//, '');
}

function dirname(path) {
     return path.match(/.*\//);
}

Sample:

Input                       dirname()           basename()
/folder/subfolder/file.ext  /folder/subfolder/  file.ext
/folder/subfolder           /folder/            subfolder
/file.ext                   file.ext            /
file.ext                    file.ext            null

See reference.

Comments

1

A nice one line, using ES6 arrow functions:

var basename = name => /([^\/\\]*|\.[^\/\\]*)\..*$/gm.exec(name)[1];
// In response to @IAM_AL_X's comments, even shorter and it
// works with files that don't have extensions:
var basename = name => /([^\/\\\.]*)(\..*)?$/.exec(name)[1];

2 Comments

Three comments. (1) The parenthesized expression has a pipe, ie. a '|', i.e. an "or", separating two sub-expressions, but the 2nd expression is indistinguishable from the 1st. The 1st says "anything but slash or back-slash," the 2nd says "a dot followed by (the same);" but the "dot" part of the 2nd expression fulfills the "anything but" condition of the 1st, so they are effectively identical. (2) You require that a "dot" must be present, i.e. you assume the filename include a dot suffix. (3) You provide the flags "gm"; it seems rare, if ever, that those flags provide something useful, here.
@IAM_AL_X, thank you for the most useful comment I have ever received on this platform.
0

Fairly simple using regex:

function basename(input) {
   return input.split(/\.[^.]+$/)[0];
}

Explanation:

Matches a single dot character, followed by any character except a dot ([^.]), one or more times (+), tied to the end of the string ($).

It then splits the string based on this matching criteria, and returns the first result (ie everything before the match).

[EDIT] D'oh. Misread the question -- he wants to strip off the path as well. Oh well, this answers half the question anyway.

Comments

0
my_basename('http://www.example.com/dir/file.php?param1=blabla#cprod',   '/',  '?');
// returns:  file.php


CODE:

function my_basename(str, DirSeparator, FileSeparator) { var x= str.split(DirSeparator); return x[x.length-1].split(FileSeparator)[0];}

Comments

0

UPDATE

An improved version which works with forward / and backslash \ single or double means either of the following

  • \\path\\to\\file
  • \path\to\file
  • //path//to//file
  • /path/to/file
  • http://url/path/file.ext
  • http://url/path/file

See a working demo below

let urlHelper = {};
urlHelper.basename = (path) => {
  let isForwardSlash = path.match(/\/{1,2}/g) !== null;
  let isBackSlash = path.match(/\\{1,2}/g) !== null;

  if (isForwardSlash) {
    return path.split('/').reverse().filter(function(el) {
      return el !== '';
    })[0];
  } else if (isBackSlash) {
    return path.split('\\').reverse().filter(function(el) {
      return el !== '';
    })[0];
  }
  return path;
};

$('em').each(function() {
  var text = $(this).text();
  $(this).after(' --> <strong>' + urlHelper.basename(text) + '</strong><br>');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<em>http://folder/subfolder/file.ext</em><br>
<em>http://folder/subfolder/subfolder2</em><br>
<em>/folder/subfolder</em><br>
<em>/file.ext</em><br>
<em>file.ext</em><br>
<em>/folder/subfolder/</em><br>
<em>//folder//subfolder//</em><br>
<em>//folder//subfolder</em><br>
<em>\\folder\\subfolder\\</em><br>
<em>\\folder\\subfolder\\file.ext</em><br>
<em>\folder\subfolder\</em><br>
<em>\\folder\\subfolder</em><br>
<em>\\folder\\subfolder\\file.ext</em><br>
<em>\folder\subfolder</em><br>


A more simpler solution could be

function basename(path) {
      return path.replace(/\/+$/, "").replace( /.*\//, "" );
}

Input                           basename()
/folder/subfolder/file.ext   --> file.ext
/folder/subfolder            --> subfolder
/file.ext                    --> file.ext
file.ext                     --> file.ext
/folder/subfolder/           --> subfolder

Working example: https://jsfiddle.net/Smartik/2c20q0ak/1/

2 Comments

This function is not even valid. And it return nothing if the path ends with slash.... Ehh!
you are right about the trailing slash it returns nothing , and that is logical as the basename is mostly used for extracting filename from the url, let me check if i can update it to work for paths ending in /
0

If your original string or text file contains a single backslash character, you could locate it by using '\\'.

In my circumstance, I am using JavaScript to find the index of "\N" from a text file. And str.indexOf('\\N'); helped me locate the \N from the original string, which is read from the source file.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.