13
\$\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\$
  • \$\begingroup\$ Is the line-numbering continuous or short it "reset" itself for each individual file? \$\endgroup\$ – britishtea Sep 30 '14 at 23:57
  • \$\begingroup\$ @britishtea It is continuous \$\endgroup\$ – user16402 Oct 1 '14 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 Oct 2 '15 at 15:02
  • 1
    \$\begingroup\$ Binary files? Encoding? Unicode markers? \$\endgroup\$ – edc65 Oct 4 '15 at 16:51
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\$
  • 1
    \$\begingroup\$ You can make your if expressions quite a bit shorter if you use arithmetic expressions, e.g. (($#>64))&&s=2 \$\endgroup\$ – Digital Trauma Sep 29 '14 at 23:51
  • 2
    \$\begingroup\$ @DigitalTrauma I learned a thing! \$\endgroup\$ – Sammitch Sep 30 '14 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 Sep 30 '14 at 4:12
  • 1
    \$\begingroup\$ codegolf.stackexchange.com/a/25802/11259 \$\endgroup\$ – Digital Trauma Sep 30 '14 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\$ – Digital Trauma Sep 30 '14 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\$
  • \$\begingroup\$ I think STDIN is aliased to $<. \$\endgroup\$ – Martin Ender Sep 30 '14 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 Sep 30 '14 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 Oct 4 '14 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 Oct 4 '14 at 12:47
2
\$\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\$
  • \$\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 Dec 23 '15 at 17:01
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\$
  • \$\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 Sep 30 '14 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\$
  • \$\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\$ – Anders Kaseorg Oct 3 '15 at 5:01
  • \$\begingroup\$ @AndersKaseorg fixed. \$\endgroup\$ – daniero Oct 3 '15 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\$
  • \$\begingroup\$ This does not satisfy the specification regarding the 64 argument maximum and the exit status. \$\endgroup\$ – Anders Kaseorg Oct 3 '15 at 14:37
  • \$\begingroup\$ @AndersKaseorg Fixed! \$\endgroup\$ – Gowtham Oct 3 '15 at 14:57
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, privacy policy and cookie policy