13

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/'*
1
  • You could make that an answer! (If it adds something new to the existing answers - but it looks like it does) Jun 24, 2015 at 16:13

7 Answers 7

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 
    ext=""; 
    [[ $f =~ \. ]] && ext="."${f#*.}; 
    mv "$f" "${f%%.*}"_$(wc -l < "$f")$ext; 
done
7
  • 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...) Jun 24, 2015 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, 2015 at 16:37
  • thanks a lot ;) I totally forgot that it should be executed from the command line :) Jun 24, 2015 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, 2015 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, 2015 at 11:24
10

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
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
8
  • rename great =) +1
    – A.B.
    Jun 24, 2015 at 16:16
  • 1
    Wouldn't this fail on a.b.c.d? Change the second capture group to (\.?[^\.]+).
    – xyz
    Jun 24, 2015 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, 2015 at 20:16
  • @kos you are correct. How about ([^\.]+).*?([^\.]+$) I am not sure about optimality. Working fiddle: regex101.com/r/rQ9lX1/1
    – xyz
    Jun 24, 2015 at 20:27
  • 1
    @prakharsingh95 you don't want to escape . inside character classes.
    – terdon
    Jun 24, 2015 at 21:22
5

Using find:

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

Example

% 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
2
  • Why the -name "*"? Leftover from tests? ;)
    – kos
    Jun 24, 2015 at 16:19
  • 1
    @kos Old habit :\
    – A.B.
    Jun 24, 2015 at 17:07
3

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' *
1
  • Interesting idea +1
    – A.B.
    Jun 25, 2015 at 20:42
2

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

#!/bin/bash
# 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 ?
WORKINGDIR=/home/xieerqi/substitutions
# Where do you want them to go ?
OUTPUTDIR=/home/xieerqi/substitutions/output

for file in $WORKINGDIR/* ;do 
    FLAG=0
    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
        EXT=""
        FILENAME=$(printf "%s" "$file" | awk -F '/' '{ print $NF}' )
        # set flag for deciding how to rename
        FLAG=1
    else
        FILENAME=$( printf "%s" "$file" | awk -F '/' -v var=$EXT '{gsub("."var,"");print $NF}'   ) # filename, without path, lst in
    fi

    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
    else
        echo "$file" renamed as "$OUTPUTDIR"/"$FILENAME"_"$NUMLINES""$EXT"
        # cp "$file" "$OUTPUTDIR"/"$FILENAME"_"$NUMLINES""$EXT" # uncomment when necessary
    fi

    #printf "\n"

done

Script in action

$./renamer.sh                                                                                                           
/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

2

Another bash way, developed with @Helio in chat:

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

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
do
    [[ $file =~ (.*)(\.[^.]+)$ ]]
    cp "$file" "output/${BASH_REMATCH[1]:-$file}_$(wc -l < "$file")${BASH_REMATCH[2]}"
done
1
  • +1: I wouldn't be able to do a explaination for the command. ;-)
    – 0x2b3bfa0
    Jun 27, 2015 at 7:01

You must log in to answer this question.

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