How to only get file name with Linux ‘find’?
In GNU find you can use -printf parameter for that, e.g.:
find /dir1 -type f -printf "%f\n"
@Urchin No reason it shouldn’t so long as you have correct logic (i.e. -o has lower precedence than implied -a , so you will often want to group your -o arguments)
I don’t think this is the right answer. find -printf «%f\n» -exec echo <> \; reveals that the original filename is passed forward
If your find doesn’t have a -printf option you can also use basename:
find ./dir1 -type f -exec basename <> \;
Use -execdir which automatically holds the current file in <> , for example:
find . -type f -execdir echo '<>' ';'
You can also use $PWD instead of . (on some systems it won’t produce an extra dot in the front).
If you still got an extra dot, alternatively you can run:
find . -type f -execdir basename '<>' ';'
-execdir utility [argument . ] ;
The -execdir primary is identical to the -exec primary with the exception that utility will be executed from the directory that holds the current file.
When used + instead of ; , then <> is replaced with as many pathnames as possible for each invocation of utility. In other words, it’ll print all filenames in one line.
If you are using GNU find
Or you can use a programming language such as Ruby(1.9+)
If you fancy a bash (at least 4) solution
shopt -s globstar for file in **; do echo $; done
If you want to run some action against the filename only, using basename can be tough.
find ~/clang+llvm-3.3/bin/ -type f -exec echo basename <> \;
will just echo basename /my/found/path . Not what we want if we want to execute on the filename.
But you can then xargs the output. for example to kill the files in a dir based on names in another dir:
cd dirIwantToRMin; find ~/clang+llvm-3.3/bin/ -type f -exec basename <> \; | xargs rm
find /dir1 -type f -exec basename <> \;
As others have pointed out, you can combine find and basename , but by default the basename program will only operate on one path at a time, so the executable will have to be launched once for each path (using either find . -exec or find . | xargs -n 1 ), which may potentially be slow.
If you use the -a option on basename , then it can accept multiple filenames in a single invocation, which means that you can then use xargs without the -n 1 , to group the paths together into a far smaller number of invocations of basename , which should be more efficient.
find /dir1 -type f -print0 | xargs -0 basename -a
Here I’ve included the -print0 and -0 (which should be used together), in order to cope with any whitespace inside the names of files and directories.
Here is a timing comparison, between the xargs basename -a and xargs -n1 basename versions. (For sake of a like-with-like comparison, the timings reported here are after an initial dummy run, so that they are both done after the file metadata has already been copied to I/O cache.) I have piped the output to cksum in both cases, just to demonstrate that the output is independent of the method used.
$ time sh -c 'find /usr/lib -type f -print0 | xargs -0 basename -a | cksum' 2532163462 546663 real 0m0.063s user 0m0.058s sys 0m0.040s $ time sh -c 'find /usr/lib -type f -print0 | xargs -0 -n 1 basename | cksum' 2532163462 546663 real 0m14.504s user 0m12.474s sys 0m3.109s
As you can see, it really is substantially faster to avoid launching basename every time.
Extract file basename without path and extension in bash [duplicate]
This code should work in most cases as long as the value of $1 is actually what you think it is. However, it is subject to word splitting and filename expansion due to improper quoting.
Closer @tripleee: this is not a duplicate of Extract filename and extension in Bash. The other more complex Q requires the extension and filename be separate. If anything, the other Q. is an elaboration of this more basic one.
@agc The answers seem very similar, but the older question has more of them. Can you explain why you think these should be kept separate?
@tripleee Users interested in the simpler problem might be needlessly confused by the added or differing code required to solve the more complex problem.
Did you read the answers to the other question? Out of the top five, four very distinctly demonstrate how to do exactly this, and explain the options, most of them quite succinctly. I’m happy to be convinced if you can point to actual differences, but I’m not seeing them. Perhaps raise this on Meta Stack Overflow for broader visibility.
9 Answers 9
You don’t have to call the external basename command. Instead, you could use the following commands:
$ s=/the/path/foo.txt $ echo "$" foo.txt $ s=$ $ echo "$" foo $ echo "$" foo
Note that this solution should work in all recent (post 2004) POSIX compliant shells, (e.g. bash , dash , ksh , etc.).
fantastic answer. bash String Manipulations ( like $ ) are explained here linuxgazette.net/18/bash.html
@Droogans found it after some digging 🙂 tldp.org/LDP/LG/issue18/bash.html didn’t realise I had 27 upvotes on this comment 🙂
The basename command has two different invocations; in one, you specify just the path, in which case it gives you the last component, while in the other you also give a suffix that it will remove. So, you can simplify your example code by using the second invocation of basename. Also, be careful to correctly quote things:
fbname=$(basename "$1" .txt) echo "$fbname"
@handuel Unfortunately, basename does not support wildcards. Providing a second argument will only remove that exact literal string from the end.
On machines with basename 8.4, it works just as just as specified in this answer, but for me (with basename 8.22 from GNU coreutils) this worked as basename -s
@w4etwetewtwet — You can have basename remove any extension, see stackoverflow.com/a/36341390/2707864
A combination of basename and cut works fine, even in case of double ending like .tar.gz :
fbname=$(basename "$fullfile" | cut -d. -f1)
Would be interesting if this solution needs less arithmetic power than Bash Parameter Expansion.
This is my preferred way — with the minor change of using $(..) — so this becomes: fbname=$(basename «$fullfile» | cut -d. -f1)
If a file has dots elsewhere in the name, this would truncate it incorrectly. This may work better: fbname=$(basename «$fullfile» | sed -r ‘s|^(.*?)\.\w+$|\1|’) . More choices: : ‘s|^(.*?)\..+$|\1|’ , ‘s|^(.*?)\.[^\.]+$|\1|’ .
I needed this, the same as asked by bongbang and w4etwetewtwet.
$ s=/the/path/foo.txt $ echo "$(basename "$")" foo $ echo "$(basename "$" ".$")" foo
Pure bash , no basename , no variable juggling. Set a string and echo :
Note: the bash extglob option must be «on», (Ubuntu sets extglob «on» by default), if it’s not, do:
- $$p.
- // substitute every instance of the pattern that follows.
- +( match one or more of the pattern list in parenthesis, (i.e. until item #7 below).
- 1st pattern: *\/ matches anything before a literal » / » char.
- pattern separator | which in this instance acts like a logical OR.
- 2nd pattern: .* matches anything after a literal » . » — that is, in bash the » . » is just a period char, and nota regex dot.
- ) end pattern list.
- > end parameter expansion. With a string substitution, there’s usually another / there, followed by a replacement string. But since there’s no / there, the matched patterns are substituted with nothing; this deletes the matches.
Relevant man bash background:
$ Pattern substitution. The pattern is expanded to produce a pat tern just as in pathname expansion. Parameter is expanded and the longest match of pattern against its value is replaced with string. If pattern begins with /, all matches of pattern are replaced with string. Normally only the first match is replaced. If pattern begins with #, it must match at the begin‐ ning of the expanded value of parameter. If pattern begins with %, it must match at the end of the expanded value of parameter. If string is null, matches of pattern are deleted and the / fol lowing pattern may be omitted. If parameter is @ or *, the sub stitution operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is an array variable subscripted with @ or *, the substitution operation is applied to each member of the array in turn, and the expansion is the resultant list.
If the extglob shell option is enabled using the shopt builtin, several extended pattern matching operators are recognized. In the following description, a pattern-list is a list of one or more patterns separated by a |. Composite patterns may be formed using one or more of the fol lowing sub-patterns: ?(pattern-list) Matches zero or one occurrence of the given patterns *(pattern-list) Matches zero or more occurrences of the given patterns +(pattern-list) Matches one or more occurrences of the given patterns @(pattern-list) Matches one of the given patterns !(pattern-list) Matches anything except one of the given patterns