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/'*
Federico Poloni
  • 697
  • 1
  • 7
  • 22
Martin Yeboah
  • 241
  • 4
  • 11

7 Answers7

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
terdon
  • 104,404
  • 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
  • @terdon Oh, I see. Didn't look too closely at the other answers. This is the accepted one most people will see so refining it makes the most sense in my book. Cheers! :) – musiKk Jun 25 '15 at 11:27
  • @musiKk I don't know about you but "wc -l < $f" doesn't rename the files with "_" – Martin Yeboah Jun 28 '15 at 18:19
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
heemayl
  • 94,145
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
kos
  • 41,378
  • 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 (\.?[^\.]+). – xyz 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: https://regex101.com/r/rQ9lX1/1 – xyz Jun 24 '15 at 20:27
  • @prakharsingh95 Doesn't match a filename ending with a ., also the problem is that .*? even if grouped includes the last ., but the count of the lines should be inserted before it. That's not easy at all, i'm trying to grok this from at least an half an hour – kos Jun 24 '15 at 20:39
  • Well, this (https://regex101.com/r/qT5tM3/1) is matching even those cases. Hope this works out! You can add a . separately while replacing. I am writing the regex keeping in mind the capture group values. – xyz Jun 24 '15 at 20:53
  • 1
    @prakharsingh95 you don't want to escape . inside character classes. – terdon Jun 24 '15 at 21:22
  • @prakharsingh95 Thanks for your effort, however thinking about this twice I don't think it's a good thing to rename files that way: think of a (say) .tar.gz extension. Dots after all are a consolidated way of separating a filename from its extension, so unless within particular scopes and hence the for the most common use, I think the best way to add stuffs would be to add them before the first dot (consider also that sometimes a missing extension breaks the program using the file). – kos Jun 25 '15 at 20:44
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
A.B.
  • 92,275
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' *
musiKk
  • 281
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
muru
  • 207,970