I have list of files like below:

[root@ods1 backup]# ls -l
total 93892810
-rw-r----- 1 root root 651248 Feb 17 08:34  abc_def_g_17-02-2022.sql
-rw------- 1 root root 665248 Mar 23 08:46  bbc_23-03-2022.sql
-rw-r----- 1 root root 676992 Apr 04 16:52  zz_b_04-04-2022.sql

What i need to achieve is:


I tried to do this but it didn't work:

ls -l | awk '{print $9}' | cut -d '_' -f1-2

How can I achieve this goal? Any series of commands that get the lines until last _ character should do the job.

Edit: This commands are executed in CentOS 8.

6 Answers 6


To not parse ls, you can use a simple loop and shell syntax:

for f in *; do echo "${f%_*}"; done

if you need to work further with that list (and you have a shell that is capable of using arrays), you can add the items to an array:

for f in *; do tokens=("${tokens[@]}" "${f%_*}"); done
echo "${tokens[@]}"

Or without the loop, directly declare the array:

for token in "${files[@]%_*}"; do echo "$token"; done
  • That's also another useful approach. Thanks for it. Apr 21, 2022 at 6:38
  • 1
    tokens+=("${f%_*}") should work for appending to the array in the shells that support arrays. Or even tokens=(*); tokens=("${tokens[@]%_*}") for the whole deal.
    – ilkkachu
    Apr 21, 2022 at 7:12
  • ah yes, the last one is a good approach, I will add that.
    – pLumo
    Apr 21, 2022 at 7:19

You can try something like:

ls|awk -F_ '{NF-=1}1' OFS=_

Here the trick is to decrease number of fields by one (strip the last fiels)

  • 1
    Many thanks, it worked. Apr 21, 2022 at 6:37
  • 2
    If you just need to obtain a list to read for humans and/or you control the input files, that option is fine. But files are allowed to have a \n included, so it might break in a script when you cannot control input files.
    – pLumo
    Apr 21, 2022 at 6:44
  • That's how it really goes @pLumo (inputs are controlled by me), thanks for heads up anyways. Apr 21, 2022 at 10:42
  • 1
    NF-=1 can also be written NF-- however note that not all awk implementations rebuild $0 upon modifying NF. With some, including the original awk implementation, you'd need to add a $1=$1 (with a difference in behaviour if the input doesn't contain _ characters). May 6, 2022 at 7:27

In the zsh shell:

print -r -C1 -- *_*.sql(Ne['REPLY=${REPLY%_*}'])

This uses a glob qualifier that modifies the names that match the given globbing pattern *_*.sql by removing everything after and including the last underscore. The actual substitution is made using a standard parameter substitution.

If you want to use the resulting strings to do something other than printing them, then use the globbing pattern, without the print, where that makes sense (e.g., in a for loop or as an argument to some other command). Don't put the print command from above in a command substitution!

  • See also (){print -rC1 -- ${@%_*}} *_*.sql(N) May 6, 2022 at 7:23

First of all, do you need to do ls -l?  Why not just do ls, or printf '%s\n' *, to avoid generating the metadata (owner, size, date, etc.) which you don’t want?

Then do

ls | sed 's/\(.*\)_.*/\1/'

This matches the longest possible string (the first .*) that is followed by an _, and then everything after it, and replaces it with only the first string.

  • Thanks for the info and your answer is close but it still gives the strings with an underscore. I need their right end trimmed. Apr 21, 2022 at 6:32
  • 2
    Sorry; that was a typo. Apr 21, 2022 at 6:36

Don't parse the output of ls:

$ awk 'BEGIN{for (i=1;i<ARGC;i++) { f=ARGV[i]; sub(/_[^_]*$/,"",f); print f }; delete ARGV}' *

That would work even if your file names contained newlines or any other characters.


Using Raku (formerly known as Perl_6)

~$ raku -e 'for dir(test => / .+ \.sql $ /) {.Str.subst(/  _ <-[_]>*? $ /).say};'

Above is a solution coded in Raku, a member of the Perl-family of programming languages.

The solution above requires no call to ls, rather the dir() method in Raku can take a Regex-matcher, to look (for example) for files ending in .sql. Then those files can be examined individually, substituting with nothing (i.e. deleting) the two regex atom pattern _ (underscore), and the bespoke character class <-[_]>*? which frugally-matches non-underscore characters (if any) occurring $ at the end-of-string. File names ending in .sql but containing no underscores will be returned unchanged.

Nota bene, the hope of such an approach--despite being posted on a Linux/Unix SE site--is that this code will run unchanged on WSL (see SO link below).


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .