4

Title says it all. I've managed to get just the lines with this:

lines=$(wc file.txt | awk {'print $1'});

But I could use an assist appending this to the filename. Bonus points for showing me how to loop this over all the .txt files in the current directory.

codeforester
  • 39,467
  • 16
  • 112
  • 140
Andrew Hall
  • 139
  • 7
  • 3
    Are you talking about tacking the output of wc onto the end of the name of a file or onto the end of the contents of a file? [edit] your question to include concise, testable sample input and expected output. I'm amazed you got 4 upvotes for a question without an example! I've seen several questions where people put the script delimiter `'`s **inside the script** - (`awk {'foo'}` instead of `awk '{foo}'`) - where the heck is THAT idea coming from??? – Ed Morton Jan 25 '17 at 16:23

9 Answers9

6
find -name '*.txt' -execdir bash -c \
  'mv -v "$0" "${0%.txt}_$(wc -l < "$0").txt"' {} \;

where

  • the bash command is executed for each (\;) matched file;
  • {} is replaced by the currently processed filename and passed as the first argument ($0) to the script;
  • ${0%.txt} deletes shortest match of .txt from back of the string (see the official Bash-scripting guide);
  • wc -l < "$0" prints only the number of lines in the file (see answers to this question, for example)

Sample output:

'./file-a.txt' -> 'file-a_5.txt'
'./file with spaces.txt' -> 'file with spaces_8.txt'
Community
  • 1
  • 1
Ruslan Osmanov
  • 20,486
  • 7
  • 46
  • 60
  • 1
    Professional look and feel. Also, only one that uses `wc -l`. ++ – James Brown Jan 25 '17 at 09:31
  • 1
    You should replace `$(basename "$0" .txt)` with `${0%.txt}`: `basename` is definitely useless and inefficient in this case! and also maybe add `-type f` in the `find` predicates. – gniourf_gniourf Jan 25 '17 at 09:59
  • @gniourf_gniourf, indeed. Thanks. – Ruslan Osmanov Jan 25 '17 at 10:51
  • Great solution @RuslanOsmanov. What strips the leading spaces from `wc -l` output above? – codeforester Jan 27 '17 at 06:31
  • @codeforester, as far as I can see, the modern `wc` implementation doesn't print leading spaces for the first counter (and the only counter, particularly): http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/wc.c?id=e7a2580b96370da03c4d3553ccdf4ee66a14c6a4#n161 – Ruslan Osmanov Jan 27 '17 at 08:20
1

You could use the rename command, which is actually a Perl script, as follows:

rename --dry-run 'my $fn=$_; open my $fh,"<$_"; while(<$fh>){}; $_=$fn; s/.txt$/-$..txt/' *txt

Sample Output

'tight_layout1.txt' would be renamed to 'tight_layout1-519.txt'
'tight_layout2.txt' would be renamed to 'tight_layout2-1122.txt'
'tight_layout3.txt' would be renamed to 'tight_layout3-921.txt'
'tight_layout4.txt' would be renamed to 'tight_layout4-1122.txt'

If you like what it says, remove the --dry-run and run again.

The script counts the lines in the file without using any external processes and then renames them as you ask, also without using any external processes, so it quite efficient.

Or, if you are happy to invoke an external process to count the lines, and avoid the Perl method above:

rename --dry-run 's/\.txt$/-`grep -ch "^" "$_"` . ".txt"/e' *txt
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • 1
    The problem with `rename` is that it appears in many flavours. The version that comes with Perl is handy, others are much less powerful. – hek2mgl Jan 26 '17 at 07:01
  • 1
    @hek2mgl is right. For example, on my Gentoo setup `rename` comes from `sys-apps/util-linux` package (https://www.kernel.org/pub/linux/utils/util-linux/), and it doesn't support Perl, unfortunately. And the Perl version is provided by `dev-perl/rename` as `perl-rename` executable. – Ruslan Osmanov Jan 26 '17 at 07:10
0

This would work, but there are definitely more elegant ways.

for i in *.txt; do
  mv "$i" ${i/.txt/}_$(wc $i | awk {'print $1'})_.txt; 
done

Result would put the line numbers nicely before the .txt. Like:

file1_1_.txt 
file2_25_.txt
Reuben L.
  • 2,806
  • 2
  • 29
  • 45
  • Could you explain to me the reason behind that? Thanks – Reuben L. Jan 25 '17 at 04:24
  • @ReubenL. the `ls` version forks an unnecessary process, the globbing of `*.txt` into a file list happens entirely within the shell -- more efficient. Also some folks using the `ls` version could have this messed up for them by things in their own environment, like `alias ls='ls -F --color=always ...` – DouglasDD Jan 25 '17 at 04:36
  • @ReubenL. And less error prone and more idiomatic and and and – dawg Jan 25 '17 at 04:37
  • 2
    Also probably want to double-quote the destination filename (or at least the `"${i/...."` part of it) – DouglasDD Jan 25 '17 at 04:41
  • 1
    You don't need the bashism `${i/.txt}` here, `${i%.txt}` will do the job as well (and not break for a filename of the form `file.txt.lalala.txt`); as said above, you should quote _all_ the variable expansions; and if you use `wc -l < "$i"`, `awk` becomes useless (and your script will be more efficient and not break with filenames that contain newlines). – gniourf_gniourf Jan 25 '17 at 10:02
0

Use rename command

for file in *.txt; do 
 lines=$(wc ${file} | awk {'print $1'});
 rename s/$/${lines}/ ${file}
done
user13107
  • 3,239
  • 4
  • 34
  • 54
0

You could use grep -c '^' to get the number of lines, instead of wc and awk:

for file in *.txt; do
  [[ ! -f $file ]] && continue # skip over entries that are not regular files
  #
  # move file.txt to file.txt.N where N is the number of lines in file
  #
  # this naming convention has the advantage that if we run the loop again,
  # we will not reprocess the files which were processed earlier
  mv "$file" "$file".$(grep -c '^' "$file")
done
codeforester
  • 39,467
  • 16
  • 112
  • 140
0
#/bin/bash

files=$(find . -maxdepth 1 -type f -name '*.txt' -printf '%f\n')
for file in $files; do
    lines=$(wc $file | awk {'print $1'});
    extension="${file##*.}"
    filename="${file%.*}"
    mv "$file" "${filename}${lines}.${extension}"
done

You can adjust maxdepth accordingly.

Farhad Farahi
  • 35,528
  • 7
  • 73
  • 70
0

you can do like this as well:

for file in "path_to_file"/'your_filename_pattern'
    do
      lines=$(wc $file | awk {'print $1'})
      mv $file $file'_'$lines
    done

example:

    for file in /oradata/SCRIPTS_EL/text*
    do
        lines=$(wc $file | awk {'print $1'})
        mv $file $file'_'$lines
    done
Yeasir Arafat Majumder
  • 1,222
  • 2
  • 15
  • 34
0
 awk '
  BEGIN{ for ( i=1;i<ARGC;i++ ) Files[ARGV[i]]=0 }

  {Files[FILENAME]++}

  END{for (file in Files) {
        # if( file !~ "_" Files[file] ".txt$") {

           fileF=file;gsub( /\047/, "\047\"\047\"\047", fileF)
           fileT=fileF;sub( /.txt$/, "_" Files[file] ".txt", fileT)

           system( sprintf( "mv \047%s\047 \047%s\047", fileF, fileT))

        #   }
        }
     }' *.txt

Another way with awk to manage easier a second loop by allowing more control on name (like avoiding one having already the count inside from previous cycle)

Due to good remark of @gniourf_gniourf:

  • file name with space inside are possible
  • tiny code is now heavy for such a small task
NeronLeVelu
  • 9,908
  • 1
  • 23
  • 43
  • Bad design from the start: you're _mixing code and data! what if a filename contains a quote? – gniourf_gniourf Jan 25 '17 at 10:05
  • You are right, i forget to assume this. There is a second problem, empty files that are never reach (using ARGV solve this but create a problem with spacy names, ...) try several tips, but it create a gaz factory (modular but inadequat for such small request). I put last version with assumption until better – NeronLeVelu Jan 25 '17 at 13:54
  • adapted code for space and single quote inside file name – NeronLeVelu Jan 26 '17 at 08:04
0
{ linecount[FILENAME] = FNR }
END {
    linecount[FILENAME] = FNR
    for (file in linecount) {
        newname = gensub(/\.[^\.]*$/, "-"linecount[file]"&", 1, file)
        q = "'"; qq = "'\"'\"'"; gsub(q, qq, newname)
        print "mv -i -v '" gensub(q, qq, "g", file) "' '" newname "'"
    }
    close(c)
}

Save the above awk script in a file, say wcmv.awk, the run it like:

awk -f wcmv.awk *.txt

It will list the commands that need to be run to rename the files in the required way (except that it will ignore empty files). To actually execute them you can pipe the output to a shell for execution as follows.

awk -f wcmv.awk *.txt | sh

Like it goes with all irreversible batch operations, be careful and execute commands only if they look okay.

pii_ke
  • 2,811
  • 2
  • 20
  • 30
  • Bad design from the start: you're _mixing code and data!_ what if a filename contains a quote? – gniourf_gniourf Jan 25 '17 at 10:05
  • @gniourf_gniourf I think single quotes in filenames will cause no trouble now. And well, this code does look cringe worthy now. :P – pii_ke Jan 25 '17 at 16:26