0

I have a file with lines:

file1

./filled/Event_repeatFILLED.svelte
./dir2/Miscellaneous_servicesFILLED.svelte
./outline/Line_weightFILLED.svelte
./two-tone/ArchitectureFILLED.svelte
...many more lines ...

I'd like to create a file with only basenames:

file2

Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...

At the moment the following doesn't work.

sed 's/^.\///' file1 > file2

How can I do this using Shell/Bash?

4
  • You do realise that there is a command called basename which will do what you want?
    – Bib
    May 27 at 9:12
  • @Bib, it won't automatically do that for a list of filenames in a file. If you have a solution to the question, post it as answer.
    – ilkkachu
    May 27 at 9:49
  • You are already (almost) doing it with shell/bash. A large point of a shell is to glue other commands together to achieve an aim
    – roaima
    May 27 at 10:32
  • Please also remember that "the following doesn't work" doesn't really tell us much. We know something's not working because you've posted here. Instead, consider answers to questions like these: What happened? What did you expect to happen? Did you get any errors?
    – roaima
    May 27 at 10:39

3 Answers 3

4

You need to use a different delimiter in the sed substitution command. For example:

$ sed 's|.*/||' file 
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...many more lines ...

Alternatively:

$ perl -pe 's|.*/||' file 
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...many more lines ...

Or, with awk:

$ awk -F'/' '{print $NF}' file 
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...many more lines ...

Or even something silly like:

$ rev file | cut -d / -f 1 | rev
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...many more lines ...

Or you can use basename as suggested in the comments but that will be more complicated and slower:

$ while IFS= read -r fileName; do basename -- "$fileName"; done < file
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte

Finally, if you really want a pure shell solution, you can do:

$ while IFS= read -r fileName; do printf '%s\n' "${fileName##*/}"; done < file
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...many more lines ...

But don't use the two last ones, they are the least efficient and most complex. See Why is using a shell loop to process text considered bad practice?.

3
  • Note that the sed one would return $'St\xe9phane/file' for an input like $'Chazelas/St\xe9phane/file' for instance if run in a UTF-8 locale for instance. May 27 at 12:10
  • Note that most of those would return the empty string for some/dir/ or /. May 27 at 12:10
  • @StéphaneChazelas yes. I am assuming the OP wants file names, not directories and the input in the example seems to confirm that. But yes, both unicode weirdness and trailing slashes will confuse most of these approaches.
    – terdon
    May 27 at 12:17
3

With the GNU implementations of xargs and basename, you can do:

xargs -rd '\n' -a file1 basename -a -- > file2

Using basename has the advantage of handling some special cases such as / or /some/dir/ properly.

To do the equivalent by hand:

LC_ALL=C sed -e 's:^//*$:/:;t' -e 's:/*$::; s:.*/::' < file1 > file2
  • LC_ALL=C needed as file paths are not guaranteed to be made of valid text in the locale
  • / or //, ///... treated specially first
  • trailing /s removed
  • and then stripping all up to the rightmost /.

If using zsh, you could also use its :t (for tail) modifier (borrowed from csh):

print -rC1 -- ${${(f)"$(<file1)"}:t} > file2

Beware that for / (or //, ///...) it differs from GNU basename in that it returns the empty string instead of /.

With perl:

perl -MFile::Basename -lpe '$_=basename$_' < file1 > file2
3

Not to go too deeply into it, the immediate issue with your command is that the dot means "any one character" in regexes. You need .* to match an arbitrary number of characters (as many as possible in standard regexes).

So try

sed 's/^.*\///' file1 > file2

Caveats mentioned in other answers and comments apply.

1
  • 1
    @roaima, naah, I mostly just meant to point out the mistake that made their command not work. I wasn't planning on doing anything pretty here. Also the caveats mentioned in other answers and comments apply.
    – ilkkachu
    May 27 at 12:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.