Need a basename function in Javascript

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
78400 次浏览
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

function basename(url){
return ((url=/(([^\/\\\.#\? ]+)(\.\w+)*)([?#].+)?$/.exec(url))!= null)? url[2]: '';
}

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.

 basename = function(path) {
return path.replace(/.*\/|\.[^.]*$/g, '');
}

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

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.

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);

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.

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];}

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!

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/

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.

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/

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.

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];

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();
}

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.

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 ? '✅' : '❌'
};
}));

Using modern (2020) js code:

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

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);
}