I need a command to rename all files in the current working directory, in a way that the new filename will be the same as the old, but including a suffix corresponding to the number of lines of the original files (e.g. if the file f has 10 lines then it should be renamed to f_10).

Here's my (non-working) attempt:

 linenum=$(wc -l); find * -type f | grep -v sh | rename 's/^/ec/'*
  • You could make that an answer! (If it adds something new to the existing answers - but it looks like it does) – Volker Siegel Jun 24 '15 at 16:13

How about:

for f in *; do mv "$f" "$f"_$(wc -l < "$f"); done

For example:

$ wc -l *
 10 file1
 40 file2
100 file3
$ ls
file1_10  file2_40  file3_100

If you want to keep extensions (if present), use this instead:

for f in *; do 
    [[ $f =~ \. ]] && ext="."${f#*.}; 
    mv "$f" "${f%%.*}"_$(wc -l < "$f")$ext; 
  • Thanks terdon! this command line seems to get the work done but how can I change the dot (.) to( _)? Also, as my script is inside the folder, it is renamed too and hence allows my script to be executed once (because script name changes...) – Martin Yeboah Jun 24 '15 at 16:27
  • @MartinYeboah see updated answer for the _. As for the script, what script? That should be run from the command line, there's no need for a script. If you only want it to match some files, change the in * to, for example, in *txt or something. – terdon Jun 24 '15 at 16:37
  • thanks a lot ;) I totally forgot that it should be executed from the command line :) – Martin Yeboah Jun 24 '15 at 16:38
  • I think wc -l < $f instead of the grep would be easier to understand (and maybe better to perform but I haven't checked that). – musiKk Jun 25 '15 at 11:20
  • @musiKk yes, but since the other answers are using wc, I wanted to show a different approach. But OK, fair enough. – terdon Jun 25 '15 at 11:24

You can try this one liner:

find . -maxdepth 1 -type f -exec bash -c 'mv -i "$1" "$1.$(wc -l <"$1")"' _ {} \;
  • This will find all files in the current working directory (find . -maxdepth 1 -type f)

  • Then we are running a shell instance over the files found to rename the files to append the number of lines.

Example :

$ ls
bar.txt spam.txt foo.txt

$ find . -maxdepth 1 -type f -exec bash -c 'mv -i "$1" "$1.$(wc -l <"$1")"' _ {} \;

$ ls
bar.txt.12 foo.txt.24 spam.txt.7

Another way which preserves the extension (if present) using rename:

for f in *; do rename -n "s/([^.]+)(\.?.*)/\$1_$(< "$f" wc -l)\$2/" "$f"; done

If the result is the expected one, remove the -n option:

for f in *; do rename "s/([^.]+)(\.?.*)/\$1_$(< "$f" wc -l)\$2/" "$f"; done
  • rename great =) +1 – A.B. Jun 24 '15 at 16:16
  • 1
    Wouldn't this fail on a.b.c.d? Change the second capture group to (\.?[^\.]+). – ps95 Jun 24 '15 at 17:26
  • @prakharsingh95 That's true, however (\.?[^\.]+) is not ok either because it will match only up to the second dot and won't match a filename with consecutive dots or ending with a dot regardless. It's not an easy solution tough, the only way seems to do two substitutions. – kos Jun 24 '15 at 20:16
  • @kos you are correct. How about ([^\.]+).*?([^\.]+$) I am not sure about optimality. Working fiddle: regex101.com/r/rQ9lX1/1 – ps95 Jun 24 '15 at 20:27
  • 1
    @prakharsingh95 you don't want to escape . inside character classes. – terdon Jun 24 '15 at 21:22

Using find:

find . -maxdepth 1 -type f -print0 | while read -d $'\0' f; do mv "$f" "$f"_$(grep -c . "$f"); done


% wc -l *
  3 doit
  5 foo

% find . -maxdepth 1 -type f -print0 | while read -d $'\0' f; do mv "$f" "$f"_$(grep -c . "$f"); done

% wc -l *                         
  3 doit_3
  5 foo_5
  • Why the -name "*"? Leftover from tests? ;) – kos Jun 24 '15 at 16:19
  • 1
    @kos Old habit :\ – A.B. Jun 24 '15 at 17:07

Just for fun and giggles a solution with rename. Since rename is a Perl tool that accepts an arbitrary string that is eval'd, you can do all sorts of shenanigans. A solution that seems to work is the following:

rename 's/.*/open(my $f, "<", $_);my $c=()=<$f>;$_."_".$c/e' *
  • Interesting idea +1 – A.B. Jun 25 '15 at 20:42

The script bellow covers multiple cases: the single dot and extension (file.txt), multiple dots and extensions(file.1.txt), consecutive dots (file..foobar.txt), and dots in the filename (file. or file..).

The Script

# Author: Serg Kolo
# Date:  June 25,2015
# Description: script to rename files to file_numlines
# written for http://askubuntu.com/q/640430/295286

# Where are the files ?
# Where do you want them to go ?

for file in $WORKINGDIR/* ;do 
    EXT=$(printf "%s" "$file" | awk -F'.' '{printf "%s",$NF }' )  # extension, last field of dot-separated string
    # EXT="${file##*.}" # Helio's advice is to use parameter expansion, but I dont know how to use it
    if [ -z $EXT ]; then # we have a dot at the end case file. or something
        # so we gotta change extension and filename
        FILENAME=$(printf "%s" "$file" | awk -F '/' '{ print $NF}' )
        # set flag for deciding how to rename
        FILENAME=$( printf "%s" "$file" | awk -F '/' -v var=$EXT '{gsub("."var,"");print $NF}'   ) # filename, without path, lst in

    NUMLINES=$(wc -l "$file" | awk '{print $1}') # line count

    if [ $FLAG -eq 0 ];then
         echo "$file" renamed as "$OUTPUTDIR"/"$FILENAME"_"$NUMLINES"."$EXT"
        # cp "$file" "$OUTPUTDIR"/"$FILENAME"_"$NUMLINES"."$EXT" # uncomment when necessary
        echo "$file" renamed as "$OUTPUTDIR"/"$FILENAME"_"$NUMLINES""$EXT"
        # cp "$file" "$OUTPUTDIR"/"$FILENAME"_"$NUMLINES""$EXT" # uncomment when necessary

    #printf "\n"


Script in action

/home/xieerqi/substitutions/file. renamed as /home/xieerqi/substitutions/output/file._0
/home/xieerqi/substitutions/file.. renamed as /home/xieerqi/substitutions/output/file.._0
/home/xieerqi/substitutions/file.1.jpg renamed as /home/xieerqi/substitutions/output/file.1_3.jpg
/home/xieerqi/substitutions/file.1.test.jpg renamed as /home/xieerqi/substitutions/output/file.1.test_3.jpg
/home/xieerqi/substitutions/file.1.test.txt renamed as /home/xieerqi/substitutions/output/file.1.test_2.txt
/home/xieerqi/substitutions/file.1.txt renamed as /home/xieerqi/substitutions/output/file.1_2.txt
/home/xieerqi/substitutions/file.2.jpg renamed as /home/xieerqi/substitutions/output/file.2_3.jpg
/home/xieerqi/substitutions/file.2.test.jpg renamed as /home/xieerqi/substitutions/output/file.2.test_3.jpg
/home/xieerqi/substitutions/file.2.test.txt renamed as /home/xieerqi/substitutions/output/file.2.test_2.txt
/home/xieerqi/substitutions/file.2.txt renamed as /home/xieerqi/substitutions/output/file.2_2.txt
/home/xieerqi/substitutions/foo..bar.txt renamed as /home/xieerqi/substitutions/output/foo..bar_4.txt

Note that there is no lines in file. and file.. , hence line count is 0

Special thanks to terdon and Helio for reviewing the script and suggested edits


Another bash way, developed with @Helio in chat:

for file in *
    echo "$file"
    [[ -f "$file" ]] || continue
    [[ $file =~ (.*)(\.[^.]+)$ ]]
    cp "$file" "output/${BASH_REMATCH[1]:-$file}_$(wc -l < "$file")${BASH_REMATCH[2]}"

The weird looking monocled guy with a stunted second head ((.*)(\.[^.]+)$) should match only proper extensions (.foo, not ..). If there's no extension, then the BASH_REMATCH array will be empty. We can take advantage of this by using a default value for the filename ${BASH_REMATCH[1]:-$file}, and just using the extension as is.

To handle dot files, you could use find, as suggested by terdon and Helio.

find -maxdepth 1 -type f -printf '%P\0' | 
while IFS= read -r -d '' file
    [[ $file =~ (.*)(\.[^.]+)$ ]]
    cp "$file" "output/${BASH_REMATCH[1]:-$file}_$(wc -l < "$file")${BASH_REMATCH[2]}"
  • +1: I wouldn't be able to do a explaination for the command. ;-) – Helio Jun 27 '15 at 7:01

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.