12
\$\begingroup\$

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.

\$\endgroup\$
4
  • \$\begingroup\$ Is the line-numbering continuous or short it "reset" itself for each individual file? \$\endgroup\$
    – britishtea
    Commented Sep 30, 2014 at 23:57
  • \$\begingroup\$ @britishtea It is continuous \$\endgroup\$
    – user16402
    Commented Oct 1, 2014 at 7:25
  • 1
    \$\begingroup\$ So 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? \$\endgroup\$
    – DankMemes
    Commented Oct 2, 2015 at 15:02
  • 2
    \$\begingroup\$ Binary files? Encoding? Unicode markers? \$\endgroup\$
    – edc65
    Commented Oct 4, 2015 at 16:51

8 Answers 8

6
\$\begingroup\$

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)}' $@
\$\endgroup\$
9
  • 1
    \$\begingroup\$ You can make your if expressions quite a bit shorter if you use arithmetic expressions, e.g. (($#>64))&&s=2 \$\endgroup\$ Commented Sep 29, 2014 at 23:51
  • 2
    \$\begingroup\$ @DigitalTrauma I learned a thing! \$\endgroup\$
    – Sammitch
    Commented Sep 30, 2014 at 0:01
  • 1
    \$\begingroup\$ You can replace s=0;(($#>64))&&s=2 with s=$[2*($#>64)], (($s==0))|| with ((s))&& and the if statement with [ -f "$f" ]||s=1. \$\endgroup\$
    – Dennis
    Commented Sep 30, 2014 at 4:12
  • 1
    \$\begingroup\$ codegolf.stackexchange.com/a/25802/11259 \$\endgroup\$ Commented Sep 30, 2014 at 17:26
  • 2
    \$\begingroup\$ awk will also concatenate if passed multiple files, so this officially counts as a useless use of cat ;-). Instead I think this will work: awk '...' $@ \$\endgroup\$ Commented Sep 30, 2014 at 17:29
2
\$\begingroup\$

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
\$\endgroup\$
4
  • \$\begingroup\$ I think STDIN is aliased to $<. \$\endgroup\$ Commented Sep 30, 2014 at 20:29
  • \$\begingroup\$ 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). \$\endgroup\$
    – britishtea
    Commented Sep 30, 2014 at 23:41
  • \$\begingroup\$ 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 \$\endgroup\$
    – user16402
    Commented Oct 4, 2014 at 10:37
  • \$\begingroup\$ 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? \$\endgroup\$
    – britishtea
    Commented Oct 4, 2014 at 12:47
1
\$\begingroup\$

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
\$\endgroup\$
1
  • \$\begingroup\$ 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. :) \$\endgroup\$
    – Dave J
    Commented Sep 30, 2014 at 20:42
1
\$\begingroup\$

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
\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ The spec says you should handle the first 64 inputs if given more than 64, rather than just giving up at the beginning. \$\endgroup\$ Commented Oct 3, 2015 at 5:01
  • \$\begingroup\$ @AndersKaseorg fixed. \$\endgroup\$
    – daniero
    Commented Oct 3, 2015 at 11:45
1
\$\begingroup\$

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}
\$\endgroup\$
2
  • \$\begingroup\$ This does not satisfy the specification regarding the 64 argument maximum and the exit status. \$\endgroup\$ Commented Oct 3, 2015 at 14:37
  • \$\begingroup\$ @AndersKaseorg Fixed! \$\endgroup\$
    – Gowtham
    Commented Oct 3, 2015 at 14:57
1
\$\begingroup\$

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 ;)
\$\endgroup\$
1
  • \$\begingroup\$ Great answer, and welcome to Programming Puzzles and Code Golf! Perhaps you could edit to add an explanation of how your code works. \$\endgroup\$
    – wizzwizz4
    Commented Dec 23, 2015 at 17:01
0
\$\begingroup\$

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;}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.