readr write_lines() in R: Write Text Lines to a File
The readr write_lines() function writes a character vector to a file, placing one element on each line. It is the fast, pipe-friendly way to save plain text such as logs, IDs, or generated code from R.
write_lines(x, "out.txt") # write a character vector write_lines(x, "out.txt", append = TRUE) # append instead of overwrite write_lines(as.character(1:5), "n.txt") # coerce non-character input write_lines(x, "out.txt", sep = "\r\n") # Windows line endings write_lines(x, "out.txt", na = "") # blank string for NA values write_lines(x, stdout()) # write straight to the console
Need explanation? Read on for examples and pitfalls.
What write_lines() does
write_lines() saves a character vector as plain text, one element per line. It belongs to the readr package and is the counterpart of read_lines(), which pulls the same file back into R as a character vector. Unlike write_csv(), it has no notion of rows and columns: it treats your input as a flat sequence of strings and appends a line separator after each one.
The function is built for non-rectangular text. Reach for it when you have a vector of file paths, gene IDs, URLs, or generated SQL statements and want each value on its own line. Because it returns its input invisibly, write_lines() slots cleanly into a pipe without breaking the chain.
Syntax and arguments
write_lines() takes a vector plus a destination and a few formatting controls. The full signature is short, and every argument after file has a sensible default.
The arguments you will actually touch:
- x: the character vector to write. Non-character input is coerced with
as.character(). - file: the output path. A connection such as
stdout()orgzfile()also works. - sep: the string placed after every element. Keep the default
"\n"for normal text files. - na: how
NAvalues are rendered on disk. Defaults to the literal text"NA". - append: set
TRUEto add lines to an existing file instead of replacing it.
path. readr 1.4.0 renamed path to file across all write functions. Code written for older readr that passes path = will fail on current versions, so update it to file =.Four ways to use write_lines()
Write a character vector to a file
The simplest call passes a vector and a path. Here a three-element vector is written to a temporary file, then read straight back to confirm the round trip.
Each element landed on its own line. read_lines() reverses the operation exactly, returning the original vector.
Append lines to an existing file
Set append = TRUE to extend a file rather than overwrite it. This is how you build up a log file across several calls.
The two new values were added after the original three. With append = FALSE (the default) the file would have been replaced.
Control how missing values are written
The na argument decides what NA looks like on disk. By default a missing value becomes the text "NA", but you can substitute an empty string or any placeholder.
The middle line is now blank instead of reading NA, which is often what downstream tools expect.
Change the line separator
The sep argument lets you write CRLF endings or pack values onto one line. Pass "\r\n" for Windows-style endings, or a tab to keep everything on a single row.
Note that sep is appended after every element, including the last, so the file ends with a trailing separator.
write_csv() or write_tsv(), which preserve that structure.write_lines() vs writeLines() and other writers
base R already has writeLines(), so why use the readr version? write_lines() is faster on large vectors, returns its input invisibly so it works in pipes, and supports append and na directly. The table below shows where each writer fits.
| Function | Writes | Output shape | Returns |
|---|---|---|---|
write_lines() |
character vector | one element per line | x (invisibly) |
writeLines() |
character vector | one element per line | NULL |
write_csv() |
data frame | rectangular CSV | x (invisibly) |
write_file() |
single string | one unbroken blob | x (invisibly) |
The decision rule is simple. Use write_lines() for a vector of strings, write_csv() or write_tsv() for a data frame, and write_file() when you have one long string that should not be split by newlines.
write_lines(x, "out.txt") is the rough equivalent of open("out.txt", "w").writelines(line + "\n" for line in x). readr appends the separator for you, so you never add "\n" to the values yourself.Common pitfalls
A few small mistakes account for most write_lines() surprises. Watch for these.
Silent overwrite. With the default append = FALSE, calling write_lines() on an existing path replaces the whole file with no warning. If you meant to add lines, pass append = TRUE explicitly.
The old path argument. On readr 1.4.0 and later, naming the destination path raises an error.
Factors write their labels, not codes. Passing a factor coerces it with as.character(), so you get the labels. If you actually wanted the integer codes, convert with as.integer() first.
Try it yourself
Try it: Write the three strings "red", "green", "blue" to a temporary file, then append "yellow" to it. Read the file back and save the result to ex_colors.
Click to reveal solution
Explanation: The first write_lines() creates the file with three lines. The second call uses append = TRUE to add a fourth line instead of overwriting. read_lines() returns all four values.
Related readr functions
write_lines() sits in a family of readr input and output functions. These are the ones you will pair with it most often:
- read_lines() reads a text file back into a character vector.
- write_csv() writes a data frame as comma-separated values.
- write_tsv() writes a data frame with tab separators.
- write_delim() writes a data frame with any delimiter you choose.
- write_rds() saves an R object in binary form, preserving its exact structure.
For the complete argument reference, see the official readr write_lines documentation.
FAQ
What is the difference between write_lines() and writeLines()?
Both write a character vector with one element per line. write_lines() from readr is faster on large vectors, returns its input invisibly so it can be used inside a pipe, and exposes append and na arguments directly. Base R's writeLines() returns NULL and has no append argument, so adding to a file means opening a connection yourself. For tidyverse code, prefer write_lines().
Does write_lines() add a newline at the end of the file?
Yes. The sep string, "\n" by default, is appended after every element including the last one. The file therefore ends with a trailing newline, which is the standard convention for text files on Unix systems. When you read it back with read_lines(), that trailing newline does not produce an extra empty element.
How do I append lines to an existing file with write_lines()?
Pass append = TRUE. With this set, write_lines() adds your new values after the existing content instead of replacing the file. If the file does not exist yet, it is created. This makes it straightforward to build a log file incrementally across multiple calls without reading and rewriting the whole file each time.
Can write_lines() write a numeric vector?
Yes. Non-character input is coerced with as.character() before writing, so write_lines(1:5, "n.txt") produces five lines of digits. To be explicit and avoid surprises with factors or dates, wrap the input in as.character() yourself. For numbers needing specific formatting, call format() or sprintf() first.