In the example that follows, the pattern is separated by - and the length is 3. My question is: Is there a more efficient way to do this?

$ echo $foo
$ echo "$foo" | sed -E 's:([^-\.\/]{3})([^-]*):\1:g'| sed "s/$/.${foo##*.}/"
$ foo=${foo%.pdf}.djvu
$ echo $foo
$ echo "$foo" | sed -E 's:([^-\.\/]{3})([^-]*):\1:g'| sed "s/$/.${foo##*.}/"

What I ultimately need this command for, is to replace ... by it inside for f in $(find . -name pattern); do mv $f $(...);done

  • 2
    can you please explain bit more, I am having hard time to understand your question. what do you mean by "replace ... by it inside" ? – Rahul Aug 9 '16 at 4:47
  • I want to have the files that match a certain pattern have equal size. for in ...; do mv $f $(echo $f | sed -E ...) with the sed part as shown in the example. – Erwann Aug 9 '16 at 5:47
  • What pattern I want to search and outcomes is already stated at the beginning of the question, followed by an example. Here's another example of input: gtr-tomwaits-callfromistanbul-chord-ehaugen.mp4 gtr-tomwaits-callfromistanbul-mribot.mp4 and the desired output for length=6 gtr-tomwai-callfr-chord-ehauge.mp4 gtr-tomwai-callfr-mribot.mp4 – Erwann Aug 9 '16 at 6:32

You could shorten that to

echo ./abcd-123-efghij-45678.pdf | sed -E 's:([^-\.\/]{3})([^-]*):\1:g; s/$/.djvu/'

but otherwise, it's pretty much the best you can do with sed.

  • OK, and is there a better way with a command other than sed? – Erwann Aug 9 '16 at 6:52
  • You could do it in shell alone, but that would be more complicated. You could do it in awk or perl, or some other language that has a split operation. That might be easier to understand. But in general, sed is a good tool for that kind of job. – Michael Vehrs Aug 9 '16 at 9:06

I guess this would help you, For example you want to search all *.txt files

for f in $(find . -type f -name "*.txt"); 
    echo mv "$f" $(echo "$f" | sed -E 's:([^-\.\/]{3})([^-]*):\1:g;s/$/.'"${f##*.}"'/')

If you are satisfied with the result then remove echo and run the code again.


Here's a simple perl script. I doubt very much if it's more "efficient" in terms of CPU usage or memory consumption than sed but you'd be extremely unlikely to notice any difference in run-time and it's easier to read, modify, and use:

#! /usr/bin/perl

use strict;
use warnings;

# default to substring lengths of 3
my $maxlen=3;
if ($ARGV[0] =~ m/^\d+$/) {  # if first arg contains only digits
  $maxlen = shift;

while(<>) {
  s/#.*|^\s*|\s*$//g;  # strip '#' comments, leading & trailng spaces
  next if (m/^$/);     # skip blank lines

  my ($path,$ext) = '';
  my $filename = $_;

  # extract path (if any)
  if (m/^(.*\/)(.*)/o) {
    ($path,$filename) = ($1,$2)

  # extract filename extension (if any)
  if ($filename =~ m/(^.*)(\..*)$/) {
    ($filename,$ext) = ($1,$2);

  # split into fields on '-', reduce each field to $maxlen.
  my @fields=split /-/,$filename;
  @fields = map { substr($_,0,$maxlen-1) } @fields;

  print "$path" . join('-',@fields) . $ext . "\n";

Save the script with a descriptive filename, and make it executable with chmod +x scriptname. I just used your U&L name.


==> erwann1.txt <==

==> erwann2.txt <==


$ ./erwann.pl 3 erwann1.txt 

$ ./erwann.pl 6 erwann2.txt 

You could run it as:

ls -1 *.mp4 | ./erwann.pl 6
find . -type f -name '*.pdf' | ./erwann.pl 

The script will handle filenames with any characters except newlines in them. If there's any chance that filenames will have newlines in them, the script is easily modified to support NUL-terminated input records (maybe use Getopt::Std or Getopt::Long to handle command-line options)

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.