Renaming files to add a suffix
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)
7 Answers 7
$ 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="."$; mv "$f" "$"_$(wc -l < "$f")$ext; done
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. )
@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.
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 yes, but since the other answers are using wc , I wanted to show a different approach. But OK, fair enough.
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.
$ 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:
@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 you are correct. How about ([^\.]+).*?([^\.]+$) I am not sure about optimality. Working fiddle: regex101.com/r/rQ9lX1/1
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
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:
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..).
#!/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'.' '' ) # extension, last field of dot-separated string # EXT="$" # 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 '' ) # filename, without path, lst in fi NUMLINES=$(wc -l "$file" | awk '') # 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
How to rename with prefix/suffix?
How do I do mv original.filename new.original.filename without retyping the original filename? I would imagine being able to do something like mv -p=new. original.filename or perhaps mv original.filename new.~ or whatever - but I can't see anything like this after looking at man mv / info mv pages. Of course, I could write a shell script to do this, but isn't there an existing command/flag for it?
11 Answers 11
You could use the rename(1) command:
rename 's/(.*)$/new.$1/' original.filename
Edit: If rename isn't available and you have to rename more than one file, shell scripting can really be short and simple for this. For example, to rename all *.jpg to prefix_*.jpg in the current directory:
for filename in *.jpg; do mv "$filename" "prefix_$"; done;
or also, leveraging from Dave Webb's answer and using brace expansion:
for filename in *.jpg; do mv "$filename"; done;
Installed graphite this weekend, and they have a step that's essentially ("copy all the *.example files into the conf folder, removing the example suffix"). Real lazy on their part, but rename 's/(.*).example/$1/' *.example saved me from the tedium.
rename command does not work on my RHEL 6 machine. Are there any different versions of this command? The for . done worked for me.
In Bash and zsh you can do this with Brace Expansion. This simply expands a list of items in braces. For example:
# echo -ice-cream vanilla-ice-cream chocolate-ice-cream strawberry-ice-cream
So you can do your rename as follows:
mv original.filename new.original.filename
What this does not do is work with wild cards. That is, in a directory containing filenames a, b, c, using "echo <,new.>*" yields the list "a b c new.*" (because file name generation via brace expansion occurs before expanding wild card characters - at least by inference).,new.>
Since months I'm looking for such a solution only using basic shell functionality. I would have never come up with something that simple, thanks a lot!
You can achieve a unix compatible multiple file rename (using wildcards) by creating a for loop:
for file in *; do mv $file new.$ done
I've seen people mention a rename command, but it is not routinely available on Unix systems (as opposed to Linux systems, say, or Cygwin - on both of which, rename is an executable rather than a script). That version of rename has a fairly limited functionality:
It replaces the from part of the file names with the to, and the example given in the man page is:
This renames foo1 to foo01, and foo10 to foo010, etc.
I use a Perl script called rename , which I originally dug out from the first edition Camel book, circa 1992, and then extended, to rename files.
#!/bin/perl -w # # @(#)$Id: rename.pl,v 1.7 2008/02/16 07:53:08 jleffler Exp $ # # Rename files using a Perl substitute or transliterate command use strict; use Getopt::Std; my(%opts); my($usage) = "Usage: $0 [-fnxV] perlexpr [filenames]\n"; my($force) = 0; my($noexc) = 0; my($trace) = 0; die $usage unless getopts('fnxV', \%opts); if ($opts) < printf "%s\n", q'RENAME Version $Revision: 1.7 $ ($Date: 2008/02/16 07:53:08 $)'; exit 0; >$force = 1 if ($opts); $noexc = 1 if ($opts); $trace = 1 if ($opts); my($op) = shift; die $usage unless defined $op; if (!@ARGV) < @ARGV = ; chop(@ARGV); > for (@ARGV) < if (-e $_ || -l $_) < my($was) = $_; eval $op; die $@ if $@; next if ($was eq $_); if ($force == 0 && -f $_) < print STDERR "rename failed: $was - $_ exists\n"; >else < print "+ $was -->$_\n" if $trace; print STDERR "rename failed: $was - $!\n" unless ($noexc || rename($was, $_)); > > else < print STDERR "$_ - $!\n"; >>
This allows you to write any Perl substitute or transliterate command to map file names. In the specific example requested, you'd use:
rename 's/^/new./' original.filename
Rename Files and Directories (Add Prefix)
Thanks to Peter van der Heijden, here's one that'll work for filenames with spaces in them:
for f in * ; do mv -- "$f" "PRE_$f" ; done
("--" is needed to succeed with files that begin with dashes, whose names would otherwise be interpreted as switches for the mv command)
If you change the ls command to * and put double quotes around the arguments to mv , it will work for files containing spaces.
It should be "mv --" not just "mv", to guard against files starting with dashes, that would otherwise be interpreted as command switches. I will contribute an edit to correct this.
There are no problems with metacharacters or whitespace in filenames.
Some systems have a util-linux utility called "rename" which works differently (on Ubuntu it's called "rename.ul").
For adding prefix or suffix for files(directories), you could use the simple and powerful way by xargs:
ls | xargs -I <> mv <> PRE_<> ls | xargs -I <> mv <> <>_SUF
It is using the paramerter-replacing option of xargs: -I. And you can get more detail from the man page.
P.S.: If you just want to rename part files (directories) of current directory, just filter it before xargs, such as: ls *.old | xargs -I <> mv <> PRE_<>
interestingly using rename did not work. using xargs went well for my RHEL setup so +1 for this option, makes it easy to understand the command
Doesn't work if filename contains single quote, fails with: xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option
This could be done running a simple find command:
find * -maxdepth 0 -exec mv <> PRE_<> \;
The above command will prefix all files and folders in the current directory with PRE_ .
To add a prefix to all files and folders in the current directory using util-linux's rename (as opposed to prename , the perl variant from Debian and certain other systems), you can do:
This finds the first occurrence of the empty string (which is found immediately) and then replaces that occurrence with your prefix, then glues on the rest of the file name to the end of that. Done.
perl -e 'rename $_, "PRE_$_" for '
On my system, I don't have the rename command. Here is a simple one liner. It finds all the HTML files recursively and adds prefix_ in front of their names:
for f in $(find . -name '*.html'); do mv "$f" "$(dirname "$f")/prefix_$(basename "$f")"; done
Here is a simple script that you can use. I like using the non-standard module File::chdir to handle managing cd operations, so to use this script as-is you will need to install it ( sudo cpan File::chdir ).
#!/usr/bin/perl use strict; use warnings; use File::Copy; use File::chdir; # allows cd-ing by use of $CWD, much easier but needs CPAN module die "Usage: $0 dir prefix" unless (@ARGV >= 2); my ($dir, $pre) = @ARGV; opendir(my $dir_handle, $dir) or die "Cannot open directory $dir"; my @files = readdir($dir_handle); close($dir_handle); $CWD = $dir; # cd to the directory, needs File::chdir foreach my $file (@files) < next if ($file =~ /^\.+$/); # avoid folders . and .. next if ($0 =~ /$file/); # avoid moving this script if it is in the directory move($file, $pre . $file) or warn "Cannot rename file $file: $!"; >