Write a program that outputs the number of times it has been run

17

2

In a programming language of your choice, write a full program that, when run, prints a positive integer N and then modifies its own source file such that the next time it is run it will print N+1.

For example, this (ungolfed) Python 3 program satisfies this behavior:

N = 1
print(N)
with open(__file__, 'r+') as f:
    N = int(f.readline()[4:]) + 1
    code = 'N = ' + str(N) + '\n' + f.read()
    f.seek(0)
    f.write(code)

Right away it declares and prints N, then it opens itself and rewrites its first line so the declared value of N is incremented. Then it copies the rest of its code verbatim and writes the new code to the same file, resulting in a program that will print N+1 when run again.

If the file were named incr.py, running it on the command line would look something like this:

C:\somepath> python incr.py
1
C:\somepath> python incr.py
2
C:\somepath> python incr.py
3
C:\somepath> python incr.py
4

At this point, incr.py would read:

N = 5
print(N)
with open(__file__, 'r+') as f:
    N = int(f.readline()[4:]) + 1
    code = 'N = ' + str(N) + '\n' + f.read()
    f.seek(0)
    f.write(code)

Your program should behave essentially the same as this Python example, though of course your program structure may be very different, even changing as N increases. (You don't need to define N as a plain integer on the first line, for example.)

Give the program that prints 1 in your answer. Programs for all higher N can then be inferred. The shortest 1-printing program in bytes wins.

Notes

  • The output should be the sole number N in decimal (or your language's most natural base), followed optionally by a trailing newline.

  • Your program must actually rewrite its own source code file, not simply output its modified source like a quine.

  • The file name and/or path of the source file may be hardcoded into your program (e.g. 'incr.py' could have replaced __file__ in the Python example) but the program's behavior should not depend on them. If the file is moved or renamed it should be easy enough to rewrite the program accordingly.

  • The source file's name and path should not change during execution.

  • Temporary files may be made (to copy code to or whatever) but they should be removed before the main program ends. Specifically, the only side effects of running your program should be:

    1. Printing N to stdout.

    2. Changing the source file so it will print N+1 when next run.

Calvin's Hobbies

Posted 2016-08-30T01:05:10.243

Reputation: 84 000

Question was closed 2016-08-30T13:33:28.713

Related (but that one was a pop con so not a duplicate) – trichoplax – 2016-08-30T01:09:27.777

1In Excel it's just 5 bytes by setting maximum iterations in Enable Iterative Calculation to 1 and then just enter = A1 + 1 in cell A1. – Anastasiya-Romanova 秀 – 2016-08-30T02:43:05.250

@matsjoyce No. Did you try the example? It does work beyond 9. – Calvin's Hobbies – 2016-08-30T07:13:47.147

@Anastasiya-Romanova秀, the spec specifically requires you to modify the source file. There are probably relatively few languages which can do this running under Windows, because of its tendency to lock files. – Peter Taylor – 2016-08-30T07:35:22.677

3Helka, the spec also says "*modifies its own source file such that the next time it is run*" (my emphasis). If your intention is that only interpreted languages may be used, you should state that explicitly. If you intend to allow compiled languages as well, the wording is a bit unclear. – Peter Taylor – 2016-08-30T07:38:26.847

@HelkaHomba Sorry, missed the f.read(). Too early it seems... – matsjoyce – 2016-08-30T07:50:44.397

1@PeterTaylor That's why I didn't post my answer :D – Anastasiya-Romanova 秀 – 2016-08-30T07:54:16.347

Can the program receive as input it's own filename? (another way of hardcoding it). I ask because I want to solve it in sed and it absolutely requires an input to start. I will count the name in the total bytes ofcourse. – seshoumara – 2016-08-30T12:42:21.530

I don't see how this is not a duplicate of the challenge that @manatwork linked. – AdmBorkBork – 2016-08-30T12:51:24.037

@TimmyD It sadly definitely is. In my defense there were two conversations about this possibly being a dupe in chat and that challenge wasn't mentioned.

– Calvin's Hobbies – 2016-08-30T14:10:33.147

Answers

22

bash (+ wc + id), 16 bytes

wc -l<$0;id>>$0
 

Counts its lines, then appends a line of garbage to itself.

Needs a trailing newline.

Crashes on line 2 with a syntax error, which thankfully prints to STDERR and exits.

After three invocations, the script looks like this for me:

wc -l<$0;id>>$0
uid=1000(lynn) gid=1000(lynn) groups=1000(lynn),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
uid=1000(lynn) gid=1000(lynn) groups=1000(lynn),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
uid=1000(lynn) gid=1000(lynn) groups=1000(lynn),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
 

Lynn

Posted 2016-08-30T01:05:10.243

Reputation: 55 648

5Now that's style! – GreenAsJade – 2016-08-30T09:16:03.627

2Gave +1. I also want to mention that since there are no quotes around $0, the source filename must not contain spaces and other specific characters. – seshoumara – 2016-08-30T11:30:24.143

13

Python 2, 31 bytes

open(*'aa').write('+1');print 1

Simply appends +1 to the source file upon execution. This program assumes that the source file is named as a. It can be easily modified to work with an arbitrary filename at 38 bytes as follows:

open(__file__,'a').write('+1');print 1

Additionally, a 37 bytes alternative exists though it isn't valid due to the 'no temporary files' rule:

f=open(*'aa')
print>>f
print f.tell()

This program appends a newline to the file a which is assumed not to exist before the first execution of the program. Then, the program prints the file size of a which is simply f.tell() since the file object is now pointing to the end of the file after appending.

xsot

Posted 2016-08-30T01:05:10.243

Reputation: 5 069

if you know file is stored in 'a', why use __file__ at all? – Maltysen – 2016-08-30T06:18:40.850

@Maltysen I'm not assuming the filename of the source file. 'a' opens the file for appending. – xsot – 2016-08-30T06:22:28.173

Though you are allowed to name the file a.py or just a for a few byte savings. – Calvin's Hobbies – 2016-08-30T07:39:01.537

@HelkaHomba Ah, I see. I assumed it wasn't allowed. Unfortunately, this means my 37 bytes program is now completely outclassed. – xsot – 2016-08-30T08:00:03.620

6

Perl (+shell), 30 bytes

self.pl:

#!/usr/bin/perl
print 1;`$^X -pi -es/1/1+1/ $0`

Works as shown, but replace ^X by the literal control byte for the claimed score. This does lead to a warning on recent perl versions though.

Because the filename is passed through the shell it should not contain any special characters.

A pure perl version weights in at 32 bytes (or 33 if you add a -X option to suppress the -i warning)

#!/usr/bin/perl -pi
INIT{print 1;@ARGV=$0}s/1/1+1/

If you don't mind calling with perl -M5.010 in both version 2 more bytes can be removed by using say instead of print

Ton Hospel

Posted 2016-08-30T01:05:10.243

Reputation: 14 114

5

Pyth - 7 bytes

Very simple, save as o.txt.

.wk)l"1

(btw to change filename, replace the paren for the .w with a string, like: .wk"file.pyth"l"1

Maltysen

Posted 2016-08-30T01:05:10.243

Reputation: 25 023

1...what is it doing? – Francesco Dondi – 2016-08-30T09:33:56.273

1@FrancescoDondi "1 is the string literal "1", and l gives length, so this implicitly prints the length of the string, giving 1 on the first execution. .w writes its argument to o.txt with a single trailing newline; since k is the empty string, this increases the length of the string by 1 each time, incrementing the output. – TheBikingViking – 2016-08-30T10:53:32.640

5

QBasic (QB64), 47 bytes

OPEN"C:\p.bas"FOR APPEND AS 1:?LOF(1)\2-22:?#1,

Adjust the path and filename as desired. Note: you must create the file C:\p.bas with an external text-editor (I used Notepad++) and make sure it does not contain a trailing newline. The QB64 editor adds one when you save the file, which throws off the math and the bytecount. (I assume actual QBasic would do the same, not to mention the auto-formatting.)

Here's an ungolfed version (which does expect a trailing newline):

OPEN "C:\p.bas" FOR APPEND AS 1
PRINT LOF(1) \ 2 - 32
PRINT #1,

How it works

The program opens its own code file in APPEND mode. It then uses LOF to get the length of the current version in bytes. To begin with, this is 47, so we calculate LOF(1)\2-22 (where \ is integer division) and print the resulting 1.

Finally, we want to write some no-op to the end of the file. The shortest way to do this turns out to be writing nothing, with a trailing newline automatically added. Since this is Windows (or DOS), the newline is \r\n, adding two bytes and necessitating the division by 2 earlier in the code.

(Always CLOSE your files when you're done with them, kids. Unless you're golfing.)

DLosc

Posted 2016-08-30T01:05:10.243

Reputation: 21 213

3

V, 15 bytes

:e z
jñ:wñck0

Or, this readable version:

:e <C-r>z
j<C-a>ñ:wñck0

This will not work on the online interpreter, since it disallows file access.

Here is a hexdump:

00000000: 3a65 2012 7a0a 6a01 f13a 77f1 636b 30    :e .z.j..:w.ck

Explanation:

:e                   "Edit the following file:
   <C-r>z            "Insert the full path to the source file
j                    "Move down a line
 <C-a>               "And increment the first number on this line
      ñ:wñ           "Save this file. Surrounding it with 'ñ' is a fun little hack that allows this to appear on the same line
          ck         "Change this line and the line above us
            0        "To '0' (or whatever number we're on)

James

Posted 2016-08-30T01:05:10.243

Reputation: 54 537

Does :e % work in V? (Not that it would change the byte count; I'm just curious.) – Jordan – 2016-08-30T03:18:41.040

@Jordan Good question! That will not work because % is the current file being edited, which in V is the file used to give input. @z is the path to the source file. – James – 2016-08-30T03:28:18.220

3

PowerShell, 69/60 57

Further golfed and regex corrected based on feedback from TimmyD and Matt

($a=1);$a++;(gc($b=$PSCommandPath))-replace"\d+",$a|sc $b

69 for completely path and filename agnostic

($a=1)|oh;$a++;$b=$PSCommandPath;((gc $b)-replace"\d",$a)|Out-File $b

60 bytes if I can specify a fixed filename

($a=1)|oh;$a++;(gc .\a.ps1)-replace"\d",$a)|Out-File .\a.ps1

Explanation:

Sets $a to 1, returns it. Then it increments the variable, finds its own invocation path, brings that into memory and uses regex to replace digits with the value in $a, then writes to disk.

Chirishman

Posted 2016-08-30T01:05:10.243

Reputation: 389

1Would this not fail is a became double digits? Also why not use sc instead of out-file – Matt – 2016-08-30T12:27:56.123

1The second example would fail on the second pass since there is a digit in the file name. – Matt – 2016-08-30T12:46:19.320

1Welcome to PPCG! Matt has it right -- the digit-replace needs to be thought out a little better. Also, a few additional golfs such as eliminating the |oh (things left on the pipeline are implicitly printed), moving the $b assignment into the gc, and using sc instead of out-file gets you down to 57 -- ($a=1);$a++;(gc($b=$PSCommandPath))-replace'\d+',$a|sc $b – AdmBorkBork – 2016-08-30T12:56:58.397

Ah, thanks. I definitely forgot the plus in the regex, that was silly.

I left the define of $a in the pipeline and it didn't print when I was writing it, probably because I hadn't yet put the parens around it, same for why i did the define of $b that way, I knew I could get it to return the value as well as defining but I couldn't remember how.

Completely goofed on forgetting SC – Chirishman – 2016-08-30T17:42:38.040

3

Javascript (Node.js), 92 bytes

s=require('fs')
f=s.readFileSync(l=__filename)
console.log(f.length-91)
s.writeFile(l,f+';')

Prints out the length of the file minus 91, and adds a semicolon (which is ignored by the interpreter) to the final line of the file each time it is run

dtkaias

Posted 2016-08-30T01:05:10.243

Reputation: 141

1You can save 2 bytes by moving the first line into the readFileSync call (and changing 93 to 91 of course). – Matthew Crumley – 2016-08-30T14:45:21.883

2

C#, 124 bytes

namespace System.IO{class C{static void Main(){Console.Write(File.ReadAllLines("p").Length);File.AppendAllText("p","\n");}}}

Usage: Source file needs to be called p. If using Visual Studio, make sure the Build Action is set to Compile (not default for non-cs files). Also, under project properties ensure the the Output Path is the current directory (ie- empty text box) (again not a default setting).

How it works: It opens the source file and outputs the line count. Then it appends an empty line to the source file.

Pretty print version:

namespace System.IO // Put in System.IO namespace so no usings needed.
{
    class C
    {
        static void Main()
        {
            Console.Write(File.ReadAllLines("p").Length); // Write line count/run count
            File.AppendAllText("p","\n"); // Increment line count/run count
        }
    }
}

milk

Posted 2016-08-30T01:05:10.243

Reputation: 3 043

2

PHP, 38 bytes

There are so many ways in PHP to do that ... this one´s the shortest I could find:

<?=stat(n)[7]-37;fputs(fopen(n,a),1);#

print file size (initially 38) -37, append a character (1 in this case) to the comment at the end.

  • save with file name n, call with php n
  • make sure that the file contains no trailing newline
  • fclose is called implicitly when the script finishes.
  • if you pick another filename, replace 37 with the new file size -1.
  • if filename gets longer than 5 characters (including eventual quotation marks), use $n=filename for the first occurence, $n for the second.
  • if filename gets longer than 7 characters, use __FILE__ instead of the literal file name.

Titus

Posted 2016-08-30T01:05:10.243

Reputation: 13 814

2

Dyalog APL, 21 bytes

Define f as the following:

(⎕FX⊢2-⍨⍴)'⍝',⍨⎕NR'f'

⎕NR'f' the Nested Representation of f; on the first run, this is {f, (⎕FX⊢2-⍨⍴)'⍝',⍨⎕NR'f'}

'⍝',⍨ append a comment symbol, giving {f, (⎕FX⊢2-⍨⍴)'⍝',⍨⎕NR'f', }

(...) apply the following function:

⎕FX Fix definition of f

then

2-⍨ subtract two from

the length of the argument (3 on the first run)

Example run:

      f
1
      f
2
      f
3

Adám

Posted 2016-08-30T01:05:10.243

Reputation: 37 779

2

Batch, 70 55 bytes

@cmd/cset/a-1>>%0
@call:l
@echo %n:~1%
:l
@set/an=0

Ensure that the file does not end in a newline. Use the full file name to invoke the file (e.g. incr.bat). Works by appending -1 (cmd/cset/a does not output a trailing newline) to the end of the file on each run (including the first!) so that n is the negation of the desired result, then just strips off the leading minus sign when printing n.

Edit: Saved 8 bytes by removing an exit/b which had no visible effect. Saved 7 bytes by switching from @echo off to @ prefixes on each line.

Neil

Posted 2016-08-30T01:05:10.243

Reputation: 95 035

1

R, 45 bytes

f=function(){body(f)[[3]]<<-body(f)[[3]]+1;1}

Not sure if a function counts as a "full program" but calling f() will return 1,2,... within the same R session. It also satisfies the self-modifying criterion.

It is necessary to use the double-harpoon operator <<- or else f will just make a local copy of itself and modify that instead. <<- forces that to happen at the top level.

JDL

Posted 2016-08-30T01:05:10.243

Reputation: 1 135

0

JavaScript (browser) (ES6), 84 bytes

s=document.querySelector('script');f=_=>eval(s.innerHTML);n=0;s.innerHTML+='n++;n;';

Run by calling f(). Note that this is reset on page load.

I assume that modifying the <script> tag counts as rewriting the source code.

gcampbell

Posted 2016-08-30T01:05:10.243

Reputation: 551

0

PowerShell 55 53 31 Bytes

"$(gc($p='.\1.ps1'))+1"|sc $p;1

Read in the file then just add +1 to the end of the file. Powershell will evaluate that as addition so for the fifth pass the file would have ....;1+1+1+1+1 which evaluates to five!

Old version using saved integer in a variable.

($a=1);$a++;(gc .\1.ps1)-replace'=\d+',"=$a"|sc .\1.ps1

Since we can hardcode a name it saves spaces over the script determining its own name. Printer the current value of $a. Increase $a by one. Using regex, replace the digits that follow an equal sign with the now updated value. Then write back to the original file.

If I change the way I locate the first number I can get down to 53. Using a newline I change my regex to look for numbers at the end of a line. This is why I have to a semicolon to the last line to get that omitted.

$a=1
($a++);(gc .\1.ps1)-replace'\d+$',$a|sc .\1.ps1;

Matt

Posted 2016-08-30T01:05:10.243

Reputation: 1 075