Line numbering - implement nl

13

1

Your task is to implement a program similar to the nl command-line tool from the GNU core utilities.

Standard loopholes are banned.

You may not use any built-in or external function, program or utility for numbering the lines of a file or string, such as nl itself or the = command in GNU sed.

Specification

Input

The program accepts filenames as arguments. Your code does not have to be cross-platform; the filename format of the OS running the code should be used, i.e. if you happen to be on Windows, the directory separator can be \ or /.

You must be able to take 64 input files, including - if it is specified. If over 64 are given, only handle the first 64.

In the list of filenames, - represents standard input.

If filenames are given, read from the files in the order that they are given and concatenate their contents, inserting a new line between each one and at the end. If you cannot read from one or more of the filenames (because the file does not exist or you do not have read permissions for it), ignore them. If all filenames specified are invalid, output nothing.

If no filenames are given, read from standard input. Only read from standard input if no filenames are given or if - is given.

Output

The program will output, to standard output, the input with lines numbered thus (You may assume that the input has \n, \r\n or \r line endings; pick whichever is convenient for you, but specify which one):

<5 spaces>1<tab><content of line 1 of input>
<5 spaces>2<tab><content of line 2 of input>
...
<4 spaces>10<tab><content of line 10 of input>
...
<3 spaces>100<tab><content of line 100 of input>
...
...

6 characters of space are allocated for the line number, and it is inserted at the end of these characters; the rest become spaces (e.g. 1 will have 5 leading spaces, 22 will have 4 leading spaces, ...). If the input is sufficiently long, you will eventually run out of space for the line number, at line 999999. You must not output anything after line 999999.

If the input is empty, output nothing.

Exit status

The lower numbers take priority: if errors 1 and 2 were encountered, exit with status 1.

Exit with status 0 if the input was successfully received, and the lines successfully numbered and output.

Exit with status 1 if one or more of the files specified on the command line were not found or could not be read from.

Exit with status 2 if too many files (more than 64) were given.

Exit with status 3 if the input was too long (more than 999999 lines).\

Scoring

This is code-golf - shortest program wins!

I may add bonuses later for implementing certain options that nl has. There are no bonuses at the moment.

user16402

Posted 2014-09-29T19:31:40.160

Reputation:

1So is it necessary to use node if we want to submit something in js? Or can we use function args or prompt() to emulate program args and stdin? – DankMemes – 2015-10-02T15:02:58.777

1Binary files? Encoding? Unicode markers? – edc65 – 2015-10-04T16:51:57.140

Is the line-numbering continuous or short it "reset" itself for each individual file? – britishtea – 2014-09-30T23:57:53.690

@britishtea It is continuous – None – 2014-10-01T07:25:22.207

Answers

6

Bash, 121

s=$[2*($#>64)]
for f in "$@";{ [ -f $f ]||s=1;}
((s))&&exit $s
awk '{if(NR>=10**6){exit 3}printf("%6d\t%s\n",NR,$0)}' $@

Sammitch

Posted 2014-09-29T19:31:40.160

Reputation: 509

The spec says you should handle the first 64 inputs if given more than 64, rather than just giving up at the beginning. I think it also suggests that you should output all the readable files if only some of them are readable. – Anders Kaseorg – 2015-10-03T05:04:48.400

1You can make your if expressions quite a bit shorter if you use arithmetic expressions, e.g. (($#>64))&&s=2 – Digital Trauma – 2014-09-29T23:51:17.013

2@DigitalTrauma I learned a thing! – Sammitch – 2014-09-30T00:01:33.260

1You can replace s=0;(($#>64))&&s=2 with s=$[2*($#>64)], (($s==0))|| with ((s))&& and the if statement with [ -f "$f" ]||s=1. – Dennis – 2014-09-30T04:12:18.957

1http://codegolf.stackexchange.com/a/25802/11259 – Digital Trauma – 2014-09-30T17:26:26.787

2awk will also concatenate if passed multiple files, so this officially counts as a useless use of cat ;-). Instead I think this will work: awk '...' $@ – Digital Trauma – 2014-09-30T17:29:39.693

You may be able to use the fact that when undefined variables appear in arithmetic expansions, they will expand to 0, to get rid of the s=0 line. – Digital Trauma – 2014-09-30T17:31:09.667

@Sammitch Change your third line to for f in "$@";{ [ -f "$f" ]||s=1;} - saves a lot of chars. I think the quotes around $f are necessary in case your filename has spaces in it – None – 2014-09-30T18:19:11.417

@Sammitch I've tested this, but it doesn't seem to handle - for STDIN correctly – None – 2014-10-04T10:35:42.400

2

Ruby, 195

o,l=$*[64]?[2]:[],999999
($*==[]?[?-]:$*).each{|n|f=n==?-?STDIN: open(n)rescue o<<1&&next
s=f.read.lines
s[l]?o<<3:1
puts s[0..l].map.with_index(1){|l,i|i.to_s.rjust(6)+?\t+l}}
exit !o[0]?0:o.min

britishtea

Posted 2014-09-29T19:31:40.160

Reputation: 1 189

I think STDIN is aliased to $<. – Martin Ender – 2014-09-30T20:29:02.157

It's an alias for ARGF, which will read from the rest of the files given as arguments as well. I think this can be golfed down further using ARGF somehow (it even seems to recognize "-" as being stdin). – britishtea – 2014-09-30T23:41:02.500

britishteanl:4:in block in <main>': undefined method[]' for #Enumerator:0x000006002980c8 (NoMethodError)
from britishteanl:2:in each' from britishteanl:2:in<main>' - what's wrong? I ran it as ruby britishteanl folder/filename

– None – 2014-10-04T10:37:11.623

I suspect it's a difference in Ruby version. I've run the sample on both Ruby 2.0.0 and Ruby 2.1.2 without problems. What version are you using? – britishtea – 2014-10-04T12:47:49.850

2

Perl, 84 + 2 (-pl) = 86 bytes

perl -ple'BEGIN{map{-r||exit 1}@ARGV;@ARGV>63&&exit 2}$.>=1e6&&exit 3;$_=printf"%5d\t%s",$.,$_'

Deparsed:

perl -MO=Deparse -ple'BEGIN{map{-r||exit 1}@ARGV;@ARGV>63&&exit 2}$.>=1e6&&exit 3;$_=printf"%5d\t%s",$.,$_' output.txt; echo $?

BEGIN { $/ = "\n"; $\ = "\n"; }
sub BEGIN {
    map {exit 1 unless -r $_;} @ARGV;
    exit 2 if @ARGV > 63;
}
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    exit 3 if $. >= 1000000;
    $_ = printf("%5d\t%s", $., $_);
}
continue {
    die "-p destination: $!\n" unless print $_;
}
-e syntax OK

Important to know:

  • -p wraps the program given with -e in the while/continue loop
  • BEGIN code will be executed before the (implicit) main part
  • file test -r also fails if file doesn't exist !-e and defaults to testing $_, implicitly given in map { ... } @ARGV
  • $. holds the current line number
  • rest should be self-explanatory ;)

dbr

Posted 2014-09-29T19:31:40.160

Reputation: 21

Great answer, and welcome to Programming Puzzles and Code Golf! Perhaps you could edit to add an explanation of how your code works. – wizzwizz4 – 2015-12-23T17:01:39.203

1

python 173

import os,sys
c=0
l=1
for f in sys.argv[1:]:
    if c>64:exit(2)
    if not os.path.isfile(f):exit(1)
    c+=1
    for x in open(f):
        if l>=10**6:exit(3)
        print '%6d\t%s'%(l,x),;l+=1

Sammitch

Posted 2014-09-29T19:31:40.160

Reputation: 509

I think your code is currently missing the - for sys.stdin. A possible solution might be something like fh=sys.stdin if f=='-' else open(f) and then go with x=fh.readline()?! Unfortunately it does not make it any shorter though. :) – Dave J – 2014-09-30T20:42:50.947

1

J (162)

exit(((2*64<#)[exit@3:`(stdout@(,&LF)@;@(,&TAB@(6&":)&.>@>:@i.@#,&.>]))@.(1e6>#)@(<;.2)@(1!:1)@(<`3:@.('-'-:]))&.>@;@{.@(_64&(<\))) ::1:)]`(]&<&'-')@.(0=#)2}.ARGV

Explanation:

  • ]`(]&<&'-')@.(0=#)2}.ARGV: Get the command line arguments, and remove the first two (because those are the interpreter and the script file name). If the resulting list is empty, return ['-'] (i.e., as if the user had passed only -), otherwise return the list unchanged.
  • ( ... ::1:): if the inner function fails, return 1, otherwise return whatever the inner function returned.
  • ((2*64<#)[...): evaluate the inner function and throw the result away. Then, if the length of the passed list was not higher than 64, return 0, otherwise return 2.
  • &.>@;@{.@(_64&(<\)): get at most 64 elements from the list, and for each of them run the following function:
    • (1!:1)@(<`3:@.('-'-:])): if the element was -, read the contents of file descriptor 3 (stdin), otherwise read the contents of the file named by that element. If this fails (i.e. file doesn't exist), the code above will catch it and return 1.
    • exit@3:`(...)@.(1e6>#)@(<;.2): split the string on its line endings. If there are 1.000.000 or more lines, exit with status 3. Otherwise:
      • ,&TAB@(6&":)&.>@>:@i.@#: generate the numbers for each line, format them in a 6-digit column, and add a TAB to the end of each string,
      • ,&.>]: add each number to the front of each line.
      • stdout@(,&LF)@;: then output the whole thing, followed by an extra LF.
  • exit: exit with the return value of that function

marinus

Posted 2014-09-29T19:31:40.160

Reputation: 30 224

1

Ruby, 76 bytes

One byte for the p flag. Run with ruby -p nl.rb.

BEGIN{x=$*.size-65}
exit 2if$*.size==x
exit 3if$.>999999
$_="%6d"%$.+?\t+$_

stdin or file arguments are handled automatically by Ruby. It already exits with code 1 if a file argument doesn't exist. $. is the number of lines that have been read. $* is the command line arguments, and the files are popped off as the files are read. The p flag executes the BEGIN block and wraps the rest of the program inside a while-gets-print loop, using $_ as the input/output.

daniero

Posted 2014-09-29T19:31:40.160

Reputation: 17 193

The spec says you should handle the first 64 inputs if given more than 64, rather than just giving up at the beginning. – Anders Kaseorg – 2015-10-03T05:01:38.493

@AndersKaseorg fixed. – daniero – 2015-10-03T11:45:56.237

1

Perl, 125 122 bytes

@a=@ARGV;for(@a){$i++>63&&exit 2;($_ eq '-'or-e$_)or next;@ARGV=$_;while(<>){$c>1E6-2&&exit 3;printf"%5d\t%s",++$c,$_}say}

Gowtham

Posted 2014-09-29T19:31:40.160

Reputation: 571

This does not satisfy the specification regarding the 64 argument maximum and the exit status. – Anders Kaseorg – 2015-10-03T14:37:24.347

@AndersKaseorg Fixed! – Gowtham – 2015-10-03T14:57:35.040

0

C, 362 359

Just for the fun of it. ;-) Works with LF or CR/LF linefeeds.

#include<stdio.h>
#define R return
#define P printf(
e,t,l;void*f;r(){P"% 6d\t",++l);for(;(t=fgetc(f))^-1&&l<1000000;){if(ferror(f))R 1;P"%c",t);if(t==10)P"% 6d\t",++l);}P"\n");R l<1000000?0:3;}main(int c,char**v){e=c>65?2:0;for(++v;*v||c<2;++v){t=c<2||!strcmp(*v,"-")?f=stdin,0:!(f=fopen(*v,"rb"));if(t||(t=r()))e=!e|(e>t)?t:e;if(f&&f!=stdin)fclose(f);}R e;}

owacoder

Posted 2014-09-29T19:31:40.160

Reputation: 1 556