I have a script that lists a bunch of files that match a certain criteria. It outputs filenames only and there is a bunch of text that is unnecessary.

An example string is:


What I'd like to achieve in the output is:


I've been able to replace the underscores but I've had no luck in trimming the [CRC32].mkv successfully. Also I limit the number of characters and place an ellipsis at the end if they extend beyond 28 characters but even if it does not go beyond 28 characters, it still appends the ellipsis on the end.
The code for that is:

print substr( $0, 0, 28 )"[…]"}

Help on either of these problems would be much appreciated.

  • Which language are you using? Also, are all filenames in that format? – Aluísio A. S. G. Jan 6 '13 at 23:50
  • @Aluísio A. S. G. - I'm passing all of it through a bash script. Most if not all of the filenames follow that format. – user181353 Jan 7 '13 at 0:04
  • @DW Have you ever used a file manager called Ranger? It allows you to bulk-rename files using Vim (which, as you might know, allows you to do block edits vertically). I find that it actually makes complex renaming very easy (usually takes just a few simple edits). github.com/hut/ranger – user128580 Jan 7 '13 at 0:27
  • @OP I just noticed a problem with your problem. If you truncate all file names to 28 characters with ellipsis, and you have a bunch of files that are 32 characters long, where the counter is beyond 28 characters, you will be renaming files to same filename and end up with a single file. E.g., [blahblah] blahblahblahblahblah 01 and [blahblah] blahblahblahblahblah 02 both become [blahblah] blahblahblahblahb... (no longer different files) – user128580 Jan 7 '13 at 12:08

I think the shortest solution to meet all criteria is this one

awk '{
    if (match($0, "^(.*)_[^_]+$", a)) {
        print substr(a[1], 1, 27) (length(a[1]) > 27 ? "..." : "")
  • Two lines of pure bash will do it too, which is arguably even shorter, not to mention faster. See my answer. – kopischke Jan 29 '13 at 17:42
sed -e 's/_\[.*\.mkv//' -e 's/^\(.\{28\}\).*/\1.../' file.txt

The first bit strips the _[blah].mkv, and the second bit prints the first 28 characters, and puts the ... at the end - but if the string is less than 28 characters, it only prints the stripped file name, without adding the the ellipses.

If the file's extension isn't always going to be *.mkv, you can use this (in sed, $ means 'to the end of the line'):

sed -e 's/_\[.*$//' -e 's/^\(.\{28\}\).*/\1.../' file.txt
  • Good one, but I always find sed a bit of overkill for such pretty basic string manipulation. See my answer for two lines of pure bash which achieve the same result. – kopischke Jan 29 '13 at 17:45

Though awk, sed and company have their merits, they are not needed for this. You can easily achieve everything you asked for using only bash string operations and pattern matching. Assuming you have assigned your file name to $name:


will cut off the file type and bracketed CRC from $name. If you need to make 100 % sure you only cut off CRCs, you can use an extended regex instead of the above:

[[ $name =~ (.*)_\[[[:xdigit:]]{8}\]\..*$ ]] && name="${BASH_REMATCH[1]}"

The truncation of names longer than 28 chars is then achieved by:

(( ${#name} > 28 )) && name="${name::27}…"

– two lines of bash total (not counting the logic, loop or other, to get your file names into the var, and the output code, of course), no externals. The main advantage is that the code is blazing fast, as the shell never has to launch any external binaries.

  • OK, that's really neat. Definitely the best answer here. – evilsoup Jan 29 '13 at 17:57

Try this bash function (warning: not tested):

function convert_filename {
    # Regex guide:
    #   ^(.*)_?                everything since the beginning of the string,
    #                          optionally followed by an underscore
    #   \[[a-fA-F0-9]{8}\]    8 hexadecimal characters, surrounded by []
    #   \.(.\w+)$              filename extension at the end of the string
    local r="$(echo "$1" | sed -r 's/^(.*)_?\[[a-fA-F0-9]{8}\]\.(.\w+)$/\1/')"
    if (( ${#r} < 28 )); then
        # Outputs $r
        echo "$r"
        # Outputs the first 27 characters from $r followed by an ellipsis
        echo "${r::27}…"

Not the cleanest solution but you could do this:

 echo "[gg]_Magi_-_13_[DB38165F].mkv" | awk -F '_' '{print $1"_"$2"_"$3"_"$4}'

EDIT: Meh, scratch this answer. It won't give you the elipsis.

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.