Working through Real World Haskell right now. Here's a solution to a very early exercise in the book:
-- | 4) Counts the number of characters in a file
numCharactersInFile :: FilePath -> IO Int
numCharactersInFile fileName = do
contents <- readFile fileName
return (length contents)
My question is: How would you test this function? Is there a way to make a "mock" input instead of actually needing to interact with the file system to test it out? Haskell places such an emphasis on pure functions that I have to imagine that this is easy to do.
You can make your code testable by using a type-class-constrained type variable instead of IO.
First, let's get the imports out of the way.
{-# LANGUAGE FlexibleInstances #-}
import qualified Prelude
import Prelude hiding(readFile)
import Control.Monad.State
The code we want to test:
class Monad m => FSMonad m where
readFile :: FilePath -> m String
-- | 4) Counts the number of characters in a file
numCharactersInFile :: FSMonad m => FilePath -> m Int
numCharactersInFile fileName = do
contents <- readFile fileName
return (length contents)
Later, we can run it:
instance FSMonad IO where
readFile = Prelude.readFile
And test it too:
data MockFS = SingleFile FilePath String
instance FSMonad (State MockFS) where
-- ^ Reader would be enough in this particular case though
readFile pathRequested = do
(SingleFile pathExisting contents) <- get
if pathExisting == pathRequested
then return contents
else fail "file not found"
testNumCharactersInFile :: Bool
testNumCharactersInFile =
evalState
(numCharactersInFile "test.txt")
(SingleFile "test.txt" "hello world")
== 11
This way your code under test needs very little modification.
As Alexander Poluektov already pointed out, the code you are trying to test can easily be separated into a pure and an impure part.
Nevertheless I think it is good to know how to test such impure functions in haskell.
The usual approach to testing in haskell is to use quickcheck and that's what I also tend to use for impure code.
Here is an example of how you might achieve what you are trying to do which gives you kind of a mock behavior * :
import Test.QuickCheck
import Test.QuickCheck.Monadic(monadicIO,run,assert)
import System.Directory(removeFile,getTemporaryDirectory)
import System.IO
import Control.Exception(finally,bracket)
numCharactersInFile :: FilePath -> IO Int
numCharactersInFile fileName = do
contents <- readFile fileName
return (length contents)
Now provide an alternative function (Testing against a model):
numAlternative :: FilePath -> IO Integer
numAlternative p = bracket (openFile p ReadMode) hClose hFileSize
Provide an Arbitrary instance for the test environment:
data TestFile = TestFile String deriving (Eq,Ord,Show)
instance Arbitrary TestFile where
arbitrary = do
n <- choose (0,2000)
testString <- vectorOf n $ elements ['a'..'z']
return $ TestFile testString
Property testing against the model (using quickcheck for monadic code):
prop_charsInFile (TestFile string) =
length string > 0 ==> monadicIO $ do
(res,alternative) <- run $ createTmpFile string $
\p h -> do
alternative <- numAlternative p
testRes <- numCharactersInFile p
return (testRes,alternative)
assert $ res == fromInteger alternative
And a little helper function:
createTmpFile :: String -> (FilePath -> Handle -> IO a) -> IO a
createTmpFile content func = do
tempdir <- catch getTemporaryDirectory (\_ -> return ".")
(tempfile, temph) <- openTempFile tempdir ""
hPutStr temph content
hFlush temph
hClose temph
finally (func tempfile temph)
(removeFile tempfile)
This will let quickCheck create some random files for you and test your implementation against a model function.
$ quickCheck prop_charsInFile
+++ OK, passed 100 tests.
Of course you could also test some other properties depending on your usecase.
* Note about the my usage of the term mock behavior:
The term mock in the object oriented sense is perhaps not the best here. But what is the intention behind a mock?
It let's you test code that needs access to a resource that usually is
either not available at testing time
or is not easily controllable and thus not easy to verify.
By shifting the responsibility of providing such a resource to quickcheck, it suddenly becomes feasible to provide an environment for the code under test that can be verified after a test run.
Martin Fowler describes this nicely in an article about mocks :
"Mocks are ... objects pre-programmed with expectations which form a specification of the calls they are expected to receive."
For the quickcheck setup I'd say that files generated as input are "pre-programmed" such that we know about their size (== expectation). And then they are verified against our specification (== property).
For that you will need to modify the function such that it becomes:
numCharactersInFile :: (FilePath -> IO String) -> FilePath -> IO Int
numCharactersInFile reader fileName = do
contents <- reader fileName
return (length contents)
Now you can pass any mock function that takes a file path and return IO string such as:
fakeFile :: FilePath -> IO String
fakeFile fileName = return "Fake content"
and pass this function to numCharactersInFile.
The function consists from two parts: impure (reading part content as String) and pure (calculating the length of String).
The impure part cannot be "unit"-tested by definition. The pure part is just call to the library function (and of course you can test it if you want :) ).
So there is nothing to mock and nothing to unit-test in this example.
Put it another way. Consider you have an equal C++ or Java implementation (*): reading content and then calculating its length. What would you really want to mock and what would remain for testing afterwards?
(*) which is of course not the way you will do in C++ or Java, but that's offtopic.
Based on my layman's understanding of Haskell, I've come to the following conclusions:
If a function makes use of the IO monad, mock testing is going to be impossible. Avoid hard-coding the IO monad in your function.
Make a helper version of your function that takes in other functions that may do IO. The result will look like this:
numCharactersInFile' :: Monad m => (FilePath -> m String) -> FilePath -> m Int
numCharactersInFile' f filePath = do
contents <- f filePath
return (length contents)
numCharactersInFile' is now testable with mocks!
mockFileSystem :: FilePath -> Identity String
mockFileSystem "fileName" = return "mock file contents"
Now you can verify that numCharactersInFile' returns the the expected results w/o IO:
18 == (runIdentity . numCharactersInFile' mockFileSystem $ "fileName")
Finally, export a version of your original function signature for use with IO
numCharactersInFile :: IO Int
numCharactersInFile = NumCharactersInFile' readFile
So, at the end of the day, numCharactersInFile' is testable with mocks. numCharactersInFile is just a variation of numCharactersInFile'.
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 12 years ago.
Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
This has to be a common question that all programmers have from time to time.
How do I read a line from a text file? Then the next question is always how do i write it back.
Of course most of you use a high level framework in day to day programming (which are fine to use in answers) but sometimes it's nice to know how to do it at a low level too.
I myself know how to do it in C, C++ and Objective-C, but it sure would be handy to see how it's done in all of the popular languages, if only to help us make a better decision about what language to do our file io in. In particular I think it would be interesting to see how its done in the string manipulation languages, like: python, ruby and of course perl.
So I figure here we can create a community resource that we can all star to our profiles and refer to when we need to do file I/O in some new language. Not to mention the exposure we will all get to languages that we don't deal with on a day to day basis.
This is how you need to answer:
Create a new text file called "fileio.txt"
Write the first line "hello" to the text file.
Append the second line "world" to the text file.
Read the second line "world" into an input string.
Print the input string to the console.
Clarification:
You should show how to do this in one programming language per answer only.
Assume that the text file doesn't exist beforehand
You don't need to reopen the text file after writing the first line
No particular limit on the language.
C, C++, C#, Java, Objective-C are all great.
If you know how to do it in Prolog, Haskell, Fortran, Lisp, or Basic then please go right ahead.
LOLCODE
The specs are sketchy to say the least, but I did the best I could. Let the downvoting begin! :) I still find it a fun exercise.
HAI
CAN HAS STDIO?
PLZ OPEN FILE "FILEIO.TXT" ITZ "TehFilez"?
AWSUM THX
BTW #There is no standard way to output to files yet...
VISIBLE "Hello" ON TehFilez
BTW #There isn't a standard way to append to files either...
MOAR VISIBLE "World" ON TehFilez
GIMMEH LINES TehLinez OUTTA TehFilez
I HAS A SecondLine ITZ 1 IN MAH TehLinez
VISIBLE SecondLine
O NOES
VISIBLE "OH NOES!!!"
KTHXBYE
Python 3
with open('fileio.txt', 'w') as f:
f.write('hello')
with open('fileio.txt', 'a') as f:
f.write('\nworld')
with open('fileio.txt') as f:
s = f.readlines()[1]
print(s)
Clarifications
readlines() returns a list of all the lines in the file.
Therefore, the invokation of readlines() results in reading each and every line of the file.
In that particular case it's fine to use readlines() because we have to read the entire file anyway (we want its last line).
But if our file contains many lines and we just want to print its nth line, it's unnecessary to read the entire file.
Here are some better ways to get the nth line of a file in Python: What substitutes xreadlines() in Python 3?.
What is this with statement?
The with statement starts a code block where you can use the variable f as a stream object returned from the call to open().
When the with block ends, python calls f.close() automatically.
This guarantees the file will be closed when you exit the with block no matter how or when you exit the block
(even if you exit it via an unhandled exception). You could call f.close() explicitly, but what if your code raises an exception and you don't get to the f.close() call? That's why the with statement is useful.
You don't need to reopen the file before each operation. You can write the whole code inside one with block.
with open('fileio.txt', 'w+') as f:
f.write('hello')
f.write('\nworld')
s = f.readlines()[1]
print(s)
I used three with blocks to emphsize the difference between the three operations:
write (mode 'w'), append (mode 'a'), read (mode 'r', the default).
Brain***k
,------------------------------------------------>,------------------------------------------------>,------------------------------------------------>[-]+++++++++>[-]+++++++++>[-]+++++++++<<<<<[>>>>>>+>>>+<<<<<<<<<-]>>>>>>>>>[<<<<<<<<<+>>>>>>>>>-]<<<<<<<<[>>>>>>+>>+<<<<<<<<-]>>>>>>>>[<<<<<<<<+>>>>>>>>-]<<<<<<<[>>>>>>+>+<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]>>[-]<<<<<<<<[>>>>>>+>>+<<<<<<<<-]>>>>>>>>[<<<<<<<<+>>>>>>>>-]<<<<<[>>>>+>+<<<<<-]>>>>>[<<<<<+>>>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>[<<<<<<<<->>>->>>>>[-]<<<<<<<<[>>>>>>+>>+<<<<<<<<-]>>>>>>>>[<<<<<<<<+>>>>>>>>-]<<<<<[>>>>+>+<<<<<-]>>>>>[<<<<<+>>>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>][-]<<<<<<<[>>>>>+>>+<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]<<<<[>>>+>+<<<<-]>>>>[<<<<+>>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>[<<<<<<<->>>->>>>[-]<<<<<<<[>>>>>+>>+<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]<<<<[>>>+>+<<<<-]>>>>[<<<<+>>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>][-]<<<<<<[>>>>+>>+<<<<<<-]>>>>>>[<<<<<<+>>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>[<<<<<<->>>->>>[-]<<<<<<[>>>>+>>+<<<<<<-]>>>>>>[<<<<<<+>>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>]<[-]+<[-]+<<<<<<[>>>>>>[-]<<<<<<[-]]>>>>>>[[-]+<<<<<[>>>>>[-]<<<<<[-]]>>>>>[[-]+<<<<[>>>>[-]<<<<[-]]>>>>[[-]+<<<[>>>[-]<<<[-]]>>>[[-]+<<[>>[-]<<[-]]>>[[-]+<[>[-]<[-]]>[[-]+++++++++++++++++++++++++++++++++++++++++++++++++.-...>[-]<[-]]<>[-]]<<>>[-]]<<<>>>[-]]<<<<>>>>[-],------------------------------------------------>,------------------------------------------------>,------------------------------------------------>[-]+++++++++>[-]+++++++++>[-]+++++++++<<<<<[>>>>>>+>>>+<<<<<<<<<-]>>>>>>>>>[<<<<<<<<<+>>>>>>>>>-]<<<<<<<<[>>>>>>+>>+<<<<<<<<-]>>>>>>>>[<<<<<<<<+>>>>>>>>-]<<<<<<<[>>>>>>+>+<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]>>[-]<<<<<<<<[>>>>>>+>>+<<<<<<<<-]>>>>>>>>[<<<<<<<<+>>>>>>>>-]<<<<<[>>>>+>+<<<<<-]>>>>>[<<<<<+>>>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>[<<<<<<<<->>>->>>>>[-]<<<<<<<<[>>>>>>+>>+<<<<<<<<-]>>>>>>>>[<<<<<<<<+>>>>>>>>-]<<<<<[>>>>+>+<<<<<-]>>>>>[<<<<<+>>>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>][-]<<<<<<<[>>>>>+>>+<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]<<<<[>>>+>+<<<<-]>>>>[<<<<+>>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>[<<<<<<<->>>->>>>[-]<<<<<<<[>>>>>+>>+<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]<<<<[>>>+>+<<<<-]>>>>[<<<<+>>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>][-]<<<<<<[>>>>+>>+<<<<<<-]>>>>>>[<<<<<<+>>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>[<<<<<<->>>->>>[-]<<<<<<[>>>>+>>+<<<<<<-]>>>>>>[<<<<<<+>>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>]<[-]+<[-]+<<<<<<[>>>>>>[-]<<<<<<[-]]>>>>>>[[-]+<<<<<[>>>>>[-]<<<<<[-]]>>>>>[[-]+<<<<[>>>>[-]<<<<[-]]>>>>[[-]+<<<[>>>[-]<<<[-]]>>>[[-]+<<[>>[-]<<[-]]>>[[-]+<[>[-]<[-]]>[[-]+++++++++++++++++++++++++++++++++++++++++++++++++.-...>[-]<[-]]<>[-]]<<>>[-]]<<<>>>[-]]<<<<>>>>[-]]<<<<<>>>>>[-]]<<<<<<>>>>>>>[<<<<<<<<[>>>>>>+>+<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]>[-]++++++++++<<+<<<<<<+>>>>>>>>>>>[-]<<<<<[>>>+>>+<<<<<-]>>>>>[<<<<<+>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>[<<<<<->>->>>[-]<<<<<[>>>+>>+<<<<<-]>>>>>[<<<<<+>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>]<<<<[-]+<[>[-]<[-]]>[[-]+>[<[-]>[-]]<[<<<<<<<[-]<+>>>>>>>>[-]]><[-]]<<<<<<<<[>>>>>>+>>+<<<<<<<<-]>>>>>>>>[<<<<<<<<+>>>>>>>>-]>[-]++++++++++>>>[-]<<<<<<[>>>>+>>+<<<<<<-]>>>>>>[<<<<<<+>>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>[<<<<<<->>>->>>[-]<<<<<<[>>>>+>>+<<<<<<-]>>>>>>[<<<<<<+>>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>]<<<<[-]+<<[>>[-]<<[-]]>>[[-]+>[<[-]>[-]]<[<<<<<<<<[-]<+>>>>>>>>>[-]]><[-]]<<<<<<<<<++++++++++++++++++++++++++++++++++++++++++++++++.>++++++++++++++++++++++++++++++++++++++++++++++++.>++++++++++++++++++++++++++++++++++++++++++++++++.>>>>>>>>[-]]]<<<<<>>>>>[-]]<<<<<<>>>>>>>[<<<<<<<<[>>>>>>+>+<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]>[-]++++++++++<<+<<<<<<+>>>>>>>>>>>[-]<<<<<[>>>+>>+<<<<<-]>>>>>[<<<<<+>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>[<<<<<->>->>>[-]<<<<<[>>>+>>+<<<<<-]>>>>>[<<<<<+>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>]<<<<[-]+<[>[-]<[-]]>[[-]+>[<[-]>[-]]<[<<<<<<<[-]<+>>>>>>>>[-]]><[-]]<<<<<<<<[>>>>>>+>>+<<<<<<<<-]>>>>>>>>[<<<<<<<<+>>>>>>>>-]>[-]++++++++++>>>[-]<<<<<<[>>>>+>>+<<<<<<-]>>>>>>[<<<<<<+>>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>[<<<<<<->>>->>>[-]<<<<<<[>>>>+>>+<<<<<<-]>>>>>>[<<<<<<+>>>>>>-]<<<[>>+>+<<<-]>>>[<<<+>>>-][-]<<[>>[-]<[>[-]+<[-]]<[-]]>[-]>]<<<<[-]+<<[>>[-]<<[-]]>>[[-]+>[<[-]>[-]]<[<<<<<<<<[-]<+>>>>>>>>>[-]]><[-]]<<<<<<<<<++++++++++++++++++++++++++++++++++++++++++++++++.>++++++++++++++++++++++++++++++++++++++++++++++++.>++++++++++++++++++++++++++++++++++++++++++++++++.>>>>>>>>[-]]
COBOL
Since nobody else did......
IDENTIFICATION DIVISION.
PROGRAM-ID. WriteDemo.
AUTHOR. Mark Mullin.
* Hey, I don't even have a cobol compiler
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT StudentFile ASSIGN TO "STUDENTS.DAT"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD TestFile.
01 TestData.
02 LineNum PIC X.
02 LineText PIC X(72).
PROCEDURE DIVISION.
Begin.
OPEN OUTPUT TestFile
DISPLAY "This language is still around."
PERFORM GetFileDetails
PERFORM UNTIL TestData = SPACES
WRITE TestData
PERFORM GetStudentDetails
END-PERFORM
CLOSE TestFile
STOP RUN.
GetFileDetails.
DISPLAY "Enter - Line number, some text"
DISPLAY "NXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
ACCEPT TestData.
Haskell
main :: IO ()
main = let filePath = "fileio.txt" in
do writeFile filePath "hello"
appendFile filePath "\nworld"
fileLines <- readFile filePath
let secondLine = (lines fileLines) !! 1
putStrLn secondLine
If you just want to read/write a file:
main :: IO ()
main = readFile "somefile.txt" >>= writeFile "someotherfile.txt"
D
module d_io;
import std.stdio;
void main()
{
auto f = File("fileio.txt", "w");
f.writeln("hello");
f.writeln("world");
f.open("fileio.txt", "r");
f.readln;
auto s = f.readln;
writeln(s);
}
Ruby
PATH = 'fileio.txt'
File.open(PATH, 'w') { |file| file.puts "hello" }
File.open(PATH, 'a') { |file| file.puts "world" }
puts line = File.readlines(PATH).last
C#
string path = "fileio.txt";
File.WriteAllLines(path, new[] { "hello"}); //Will end it with Environment.NewLine
File.AppendAllText(path, "world");
string secondLine = File.ReadLines(path).ElementAt(1);
Console.WriteLine(secondLine);
File.ReadLines(path).ElementAt(1) is .Net 4.0 only, the alternative is File.ReadAllLines(path)[1] which parses the whole file into an array.
ANSI C
#include <stdio.h>
#include <stdlib.h>
int /*ARGSUSED*/
main(char *argv[0], int argc) {
FILE *file;
char buf[128];
if (!(file = fopen("fileio.txt", "w")) {
perror("couldn't open for writing fileio.txt");
exit(1);
}
fprintf(file, "hello");
fclose(file);
if (!(file = fopen("fileio.txt", "a")) {
perror("couldn't opened for appening fileio.txt");
exit(1);
}
fprintf(file, "\nworld");
fclose(file);
if (!(file = fopen("fileio.txt", "r")) {
perror("couldn't open for reading fileio.txt");
exit(1);
}
fgets(buf, sizeof(buf), file);
fgets(buf, sizeof(buf), file);
fclose(file);
puts(buf);
return 0;
}
Shell Script (UNIX)
#!/bin/sh
echo "hello" > fileio.txt
echo "world" >> fileio.txt
LINE=`sed -ne2p fileio.txt`
echo $LINE
Actually the sed -n "2p" part prints the second line, but the question asks for the second line to be stored in a variable and then printed, so... :)
x86 Assembler (NASM) on Linux
I haven't touched asm in 7 years, so I had to use google a bit to hack this together, but still, it works ;) I know it's not 100% correct, but hey :D
OK, it doesn't work. sorry bout this. while it does print world in the end, it doesn't print it from the file, but from the ecx which is set on line 27.
section .data
hello db 'hello',10
helloLen equ $-hello
world db 'world',10
worldLen equ $-world
helloFile db 'hello.txt'
section .text
global _start
_start:
mov eax,8
mov ebx,helloFile
mov ecx,00644Q
int 80h
mov ebx,eax
mov eax,4
mov ecx, hello
mov edx, helloLen
int 80h
mov eax,4
mov ecx, world
mov edx, worldLen
int 80h
mov eax,6
int 80h
mov eax,5
mov ebx,helloFile
int 80h
mov eax,3
int 80h
mov eax,4
mov ebx,1
int 80h
xor ebx,ebx
mov eax,1
int 80h
References used:
http://www.cin.ufpe.br/~if817/arquivos/asmtut/quickstart.html
http://bluemaster.iu.hio.no/edu/dark/lin-asm/syscalls.html
http://www.digilife.be/quickreferences/QRC/LINUX%20System%20Call%20Quick%20Reference.pdf
JavaScript - node.js
First, lots of nested callbacks.
var fs = require("fs");
var sys = require("sys");
var path = "fileio.txt";
fs.writeFile(path, "hello", function (error) {
fs.open(path, "a", 0666, function (error, file) {
fs.write(file, "\nworld", null, "utf-8", function () {
fs.close(file, function (error) {
fs.readFile(path, "utf-8", function (error, data) {
var lines = data.split("\n");
sys.puts(lines[1]);
});
});
});
});
});
A little bit cleaner:
var writeString = function (string, nextAction) {
fs.writeFile(path, string, nextAction);
};
var appendString = function (string, nextAction) {
return function (error, file) {
fs.open(path, "a", 0666, function (error, file) {
fs.write(file, string, null, "utf-8", function () {
fs.close(file, nextAction);
});
});
};
};
var readLine = function (index, nextAction) {
return function (error) {
fs.readFile(path, "utf-8", function (error, data) {
var lines = data.split("\n");
nextAction(lines[index]);
});
};
};
var writeToConsole = function (line) {
sys.puts(line);
};
writeString("hello", appendString("\nworld", readLine(1, writeToConsole)));
Common Lisp
(defun main ()
(with-open-file (s "fileio.txt" :direction :output :if-exists :supersede)
(format s "hello"))
(with-open-file (s "fileio.txt" :direction :io :if-exists :append)
(format s "~%world")
(file-position s 0)
(loop repeat 2 for line = (read-line s nil nil) finally (print line))))
PowerShell
sc fileio.txt 'hello'
ac fileio.txt 'world'
$line = (gc fileio.txt)[1]
$line
Shell Script
Here's a shell script using just builtin commands, rather than invoking external commands such as sed or tail as previous responses have done.
#!/bin/sh
echo hello > fileio.txt # Print "hello" to fileio.txt
echo world >> fileio.txt # Print "world" to fileio.txt, appending
# to what is already there
{ read input; read input; } < fileio.txt
# Read the first two lines of fileio.txt,
# storing the second in $input
echo $input # Print the contents of $input
When writing significant shell scripts, it is advisable to use builtins as much as possible, since spawning a separate process can be slow; from a quick test on my machine, the sed solution is about 20 times slower than using read. If you're going to call sed once, as in this case, it doesn't really matter much, as it will execute more quickly than you can notice, but if you're going to execute it hundreds or thousands of times, it can add up.
For those unfamiliar with the syntax, { and } execute a list of commands in the current shell environment (as opposed to ( and ) which create a subshell; we need to be operating in the current shell environment, so we can use the value of the variable later). We need to group the commands together in order to have them both operate on the same input stream, created by redirecting from fileio.txt; if we simply ran read < fileio.txt; read input < fileio.txt, we would just get the first line, as the file would be closed and re-opened between the two commands. Due to an idiosyncrasy of shell syntax ({ and } are reserved words, as opposed to metacharacters), we need to separate the { and } from the rest of the commands with spaces, and terminate the list of commands with a ;.
The read builtin takes as an argument the names of variables to read into. It consumes a line of input, breaks the input by whitespace (technically, it breaks it according to the contents of $IFS, which defaults to a space character, where a space character means split it on any of space, tab, or newline), assigns each word to the variable names given in order, and assigns the remainder of the line to the last variable. Since we're just supplying one variable, it just puts the whole line in that variable. We reuse the $input variable, since we don't care what's on the first line (if we're using Bash we could just not supply a variable name, but to be portable, you must always supply at least one name).
Note that while you can read lines one at a time, like I do here, a much more common pattern would be to wrap it in a while loop:
while read foo bar baz
do
process $foo $bar $baz
done < input.txt
Clojure
(use '[clojure.java.io :only (reader)])
(let [file-name "fileio.txt"]
(spit file-name "hello")
(spit file-name "\nworld" :append true)
(println (second (line-seq (reader file-name)))))
Or equivalently, using the threading macro -> (also known as the paren remover):
(use '[clojure.java.io :only (reader)])
(let [file-name "fileio.txt"]
(spit file-name "hello")
(spit file-name "\nworld" :append true)
(-> file-name reader line-seq second println))
F#
let path = "fileio.txt"
File.WriteAllText(path, "hello")
File.AppendAllText(path, "\nworld")
let secondLine = File.ReadLines path |> Seq.nth 1
printfn "%s" secondLine
BASIC
I haven't used BASIC in almost 10 years, but this question gave me a reason to quickly brush up my knowledge. :)
OPEN "fileio.txt" FOR OUTPUT AS 1
PRINT #1, "hello"
PRINT #1, "world"
CLOSE 1
OPEN "fileio.txt" FOR INPUT AS 1
LINE INPUT #1, A$
LINE INPUT #1, A$
CLOSE 1
PRINT A$
Objective-C
NSFileHandle *fh = [NSFileHandle fileHandleForUpdatingAtPath:#"fileio.txt"];
[[NSFileManager defaultManager] createFileAtPath:#"fileio.txt" contents:nil attributes:nil];
[fh writeData:[#"hello" dataUsingEncoding:NSUTF8StringEncoding]];
[fh writeData:[#"\nworld" dataUsingEncoding:NSUTF8StringEncoding]];
NSArray *linesInFile = [[[NSString stringWithContentsOfFile:#"fileio.txt"
encoding:NSUTF8StringEncoding
error:nil] stringByStandardizingPath]
componentsSeparatedByString:#"\n"];
NSLog(#"%#", [linesInFile objectAtIndex:1]);
Perl
#!/usr/bin/env perl
use 5.10.0;
use utf8;
use strict;
use autodie;
use warnings qw< FATAL all >;
use open qw< :std :utf8 >;
use English qw< -no_match_vars >;
# and the last shall be first
END { close(STDOUT) }
my $filename = "fileio.txt";
my($handle, #lines);
$INPUT_RECORD_SEPARATOR = $OUTPUT_RECORD_SEPARATOR = "\n";
open($handle, ">", $filename);
print $handle "hello";
close($handle);
open($handle, ">>", $filename);
print $handle "world";
close($handle);
open($handle, "<", $filename);
chomp(#lines = <$handle>);
close($handle);
print STDOUT $lines[1];
R:
cat("hello\n", file="fileio.txt")
cat("world\n", file="fileio.txt", append=TRUE)
line2 = readLines("fileio.txt", n=2)[2]
cat(line2)
PHP
<?php
$filePath = "fileio.txt";
file_put_contents($filePath, "hello");
file_put_contents($filePath, "\nworld", FILE_APPEND);
$lines = file($filePath);
echo $lines[1];
// closing PHP tags are bad practice in PHP-only files, don't use them
Java
import java.io.*;
import java.util.*;
class Test {
public static void main(String[] args) throws IOException {
String path = "fileio.txt";
File file = new File(path);
//Creates New File...
try (FileOutputStream fout = new FileOutputStream(file)) {
fout.write("hello\n".getBytes());
}
//Appends To New File...
try (FileOutputStream fout2 = new FileOutputStream(file,true)) {
fout2.write("world\n".getBytes());
}
//Reading the File...
try (BufferedReader fin = new BufferedReader(new FileReader(file))) {
fin.readLine();
System.out.println(fin.readLine());
}
}
}
C++
#include <limits>
#include <string>
#include <fstream>
#include <iostream>
int main() {
std::fstream file( "fileio.txt",
std::ios::in | std::ios::out | std::ios::trunc );
file.exceptions( std::ios::failbit );
file << "hello\n" // << std::endl, not \n, if writing includes flushing
<< "world\n";
file.seekg( 0 )
.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
std::string input_string;
std::getline( file, input_string );
std::cout << input_string << '\n';
}
or somewhat less pedantically,
#include <string>
#include <fstream>
#include <iostream>
using namespace std;
int main() {
fstream file( "fileio.txt", ios::in | ios::out | ios::trunc );
file.exceptions( ios::failbit );
file << "hello" << endl
<< "world" << endl;
file.seekg( 0 ).ignore( 10000, '\n' );
string input_string;
getline( file, input_string );
cout << input_string << endl;
}
Go
package main
import (
"os"
"bufio"
"log"
)
func main() {
file, err := os.Open("fileio.txt", os.O_RDWR | os.O_CREATE, 0666)
if err != nil {
log.Exit(err)
}
defer file.Close()
_, err = file.Write([]byte("hello\n"))
if err != nil {
log.Exit(err)
}
_, err = file.Write([]byte("world\n"))
if err != nil {
log.Exit(err)
}
// seek to the beginning
_, err = file.Seek(0,0)
if err != nil {
log.Exit(err)
}
bfile := bufio.NewReader(file)
_, err = bfile.ReadBytes('\n')
if err != nil {
log.Exit(err)
}
line, err := bfile.ReadBytes('\n')
if err != nil {
log.Exit(err)
}
os.Stdout.Write(line)
}
Erlang
Probably not the most idiomatic Erlang, but:
#!/usr/bin/env escript
main(_Args) ->
Filename = "fileio.txt",
ok = file:write_file(Filename, "hello\n", [write]),
ok = file:write_file(Filename, "world\n", [append]),
{ok, File} = file:open(Filename, [read]),
{ok, _FirstLine} = file:read_line(File),
{ok, SecondLine} = file:read_line(File),
ok = file:close(File),
io:format(SecondLine).
Emacs Lisp
Despite what some people say Emacs is mainly a text editor [1]. So while Emacs Lisp can be used to solve all kinds of problems it is optimized towards the needs of a text editor. Since text editors (obviously) have quite specific needs when it comes to how files are handled this affects what file related functionality Emacs Lisp offers.
Basically this means that Emacs Lisp does not offer functions to open a file as a stream, and read it part by part. Likewise you can't append to a file without loading the whole file first. Instead the file is completely [2] read into a buffer [3], edited and then saved to a file again.
For must tasks you would use Emacs Lisp for this is suitable and if you want to do something that does not involve editing the same functions can be used.
If you want to append to a file over and over again this comes with a huge overhead, but it is possible as demonstrated here. In practice you normally finish making changes to a buffer whether manually or programmatically before writing to a file (just combine the first two s-expressions in the example below).
(with-temp-file "file"
(insert "hello\n"))
(with-temp-file "file"
(insert-file-contents "file")
(goto-char (point-max))
(insert "world\n"))
(with-temp-buffer
(insert-file-contents "file")
(next-line)
(message "%s" (buffer-substring (point) (line-end-position))))
[1] At least I would not go as far as calling it an OS; an alternative UI yes, an OS no.
[2] You can load only parts of a file, but this can only be specified byte-wise.
[3] A buffer is both a datatype in someways similar to a string as well as the "thing you see while editing a file". While editing a buffer is displayed in a window but buffers do not necessarily have to be visible to the user.
Edit: If you want to see the text being inserted into the buffer you obviously have to make it visible, and sleep between actions. Because Emacs normally only redisplays the screen when waiting for user input (and sleeping ain't the same as waiting for input) you also have to force redisplay. This is necessary in this example (use it in place of the second sexp); in practice I never had to use `redisplay' even once - so yes, this is ugly but ...
(with-current-buffer (generate-new-buffer "*demo*")
(pop-to-buffer (current-buffer))
(redisplay)
(sleep-for 1)
(insert-file-contents "file")
(redisplay)
(sleep-for 1)
(goto-char (point-max))
(redisplay)
(sleep-for 1)
(insert "world\n")
(redisplay)
(sleep-for 1)
(write-file "file"))
Windows Batch Files - Version #2
#echo off
echo hello > fileio.txt
echo world >> fileio.txt
set /P answer=Insert:
echo %answer% >> fileio.txt
for /f "skip=1 tokens=*" %%A in (fileio.txt) do echo %%A
To explain that last horrible looking for loop, it assumes that there is only hello (newline) world in the file. So it just skips the first line and echos only the second.
Changelog
2 - Opps, must of misread the requirements or they changed on me. Now reads last line from file
Scala:
Using standard library:
val path = "fileio.txt"
val fout = new FileWriter(path)
fout write "hello\n"
fout.close()
val fout0 = new FileWriter(path, true)
fout0 write "world\n"
fout0.close()
val str = Source.fromFile(path).getLines.toSeq(1)
println(str)
Using Josh Suereth's Scala-ARM Library:
val path = "fileio.txt"
for(fout <- managed(new FileWriter(path)))
fout write "hello\n"
for(fout <- managed(new FileWriter(path, true)))
fout write "world\n"
val str = Source.fromFile(path).getLines.toSeq(1)
println(str)
Since many people have used the same file descriptor to write the two strings, I'm also including that way in my answer.
Using standard library:
val path = "fileio.txt"
val fout = new FileWriter(path)
fout write "hello\n"
fout write "world\n"
fout.close()
val str = Source.fromFile(path).getLines.toSeq(1)
println(str)
Using Josh Suereth's Scala-ARM Library:
val path = "fileio.txt"
for(fout <- managed(new FileWriter(path))){
fout write "hello\n"
fout write "world\n"
}
val str = Source.fromFile(path).getLines.toSeq(1)
println(str)
Groovy
new File("fileio.txt").with {
write "hello\n"
append "world\n"
println secondLine = readLines()[1]
}