Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 53

File I/O: Reading and Writing Buffers

Recommended resources

Introduction

In the last lesson, we learned how to programmatically delete text using delete-region, which gave us the power to modify buffer content. We built a command to clean up text, a key step toward automating your editing workflow.

So far, our scripts have only affected buffers that are already open in your Emacs session. To build truly powerful tools, we need to interact with the file system. This lesson addresses the learning outcome: Read the contents of a file into a buffer and write a buffer's content to a file. We will explore how to bridge the gap between Emacs's internal world of buffers and the external world of files, enabling you to create scripts that can read, process, and save data automatically. This is a fundamental skill for your goal of extending Emacs to manage your projects and writing.


1. Bringing File Content into Emacs

There are two primary ways to get a file's content into Emacs for programmatic manipulation. The method you choose depends on your goal.

Method 1: Visiting a File in a Hidden Buffer

When you want to operate on the entire contents of a file as a single unit, you can "visit" it. This creates a buffer that is linked to that file, but without necessarily displaying it in a window. This is ideal for background processing tasks. The key function for this is find-file-noselect.

The following video from System Crafters explains this function and introduces a very common and idiomatic Lisp pattern for working with files.

Reading and Writing Buffers in Practice - Learning Emacs Lisp #5

This clip from the 'Learning Emacs Lisp' series by System Crafters will show you how to use find-file-noselect to load a file into a buffer. Pay close attention to the use of or to elegantly handle cases where the file might already be open as a buffer.

Watch from 00:20:39 to 00:24:47. The video explains how find-file-noselect works and combines it with get-file-buffer in an or expression. This pattern is a great example of Lisp's conciseness.

The pattern shown in the video is fundamental:

(or (get-file-buffer file-path)
    (find-file-noselect file-path))

Because or evaluates its arguments in order and stops as soon as one returns a non-nil value, this expression first tries to find an existing buffer for the file. If that fails (returns nil), it then proceeds to create a new one by visiting the file. This is both efficient and robust.

Method 2: Inserting File Contents into the Current Buffer

Sometimes, you don't need to work on the file itself, but rather want to insert its contents into the buffer you're currently in. This is useful for things like inserting templates, boilerplate text, or sourcing data from another file. For this, Emacs provides insert-file-contents.

Let's consult the official manual for the details.

Reading from Files (GNU Emacs Lisp Reference Manual)

The GNU Emacs Lisp Reference Manual is the definitive source. We'll look at the chapter on Files to find the function for inserting file content directly.

This link takes you to the 'Files' chapter index. Navigate to the section titled 'Reading from Files'. Read about the function insert-file-contents. Notice its arguments, especially what it returns and how it affects the point.

As you read, you'll see that (insert-file-contents filename) does exactly what its name implies: it reads the text from filename and inserts it at the current point in the active buffer. It's a direct and simple way to pull external data into your working environment.


2. Writing Buffer Content Back to Files

Just as there are two main ways to read content, there are two corresponding ways to write it back to the disk.

Method 1: Saving a File-Backed Buffer

If you used find-file-noselect or another function to visit a file, its buffer is associated with that file path. To save any changes you've made programmatically (using insert, delete-region, etc.), you simply use save-buffer.

Reading and Writing Buffers in Practice - Learning Emacs Lisp #5

This short clip from the same System Crafters video demonstrates how to use save-buffer to persist changes.

Watch from 00:58:33 to 00:59:15. The key takeaway is the simplicity of (save-buffer). When called in a file-backed buffer, it writes the buffer's contents to the associated file.

save-buffer is straightforward but powerful. It handles the details of writing the data and is the standard way to save modifications to an existing file you are editing.

Method 2: Writing a Region to a File

What if you want to export only a part of a buffer, or write content to a brand new file without visiting it first? This is the counterpart to insert-file-contents. The function for this is write-region.

Writing to Files (GNU Emacs Lisp Reference Manual)

Let's return to the Emacs Lisp manual to learn about write-region.

From the 'Files' chapter index, navigate to the section 'Writing to Files'. Read the documentation for the write-region function. Pay attention to its arguments: start, end, filename, and the optional append flag.

The append argument is particularly useful. If nil (the default), write-region will overwrite the destination file. If non-nil (t), it will append the region's content to the end of the file. This is perfect for creating log files or collecting notes over time.


3. Your Turn: A Command to Archive Notes

Let's build a practical tool that leverages your creative writing interests. You'll create an interactive command, ar-archive-region-to-file, that takes the currently active region, appends it to a file you specify, and then deletes the region from the buffer. This could be used to quickly offload finished paragraphs, ideas, or to-do items from a scratch buffer into a permanent log.

This exercise will combine write-region with knowledge from our previous lesson.

Task: Implement the function ar-archive-region-to-file.

Here is a skeleton to get you started.

(defun ar-archive-region-to-file (start end filename)
  "Append the content of the region to FILENAME and delete the region."
  (interactive "r\nFAppend to file: ")
  ;; 1. Write the region from START to END to FILENAME.
  ;;    Make sure to append, not overwrite.
  ;;
  ;; 2. Delete the region from the current buffer.
  ;;
  ;; 3. Display a confirmation message to the user.
  )

Hints:

  • The interactive specifier "r\nFAppend to file: " does two things: "r" automatically provides the start and end arguments from the active region, and "F..." prompts for a filename.
  • The write-region function takes an append argument.
  • You already know the function to delete a region from the previous lesson.

Give it a try in your *scratch* buffer. Once you have a working version, compare it with the solution below.

Click here for the solution and explanation
(defun ar-archive-region-to-file (start end filename)
  "Append the content of the region to FILENAME and delete the region."
  (interactive "r\nFAppend to file: ")
  ;; Use 'write-region'. The fourth argument 't' means append.
  (write-region start end filename t)

  ;; Use 'delete-region' as we learned in the last lesson.
  (delete-region start end)

  ;; Let the user know it worked. 'file-name-nondirectory' makes
  ;; the message cleaner by showing just the filename, not the whole path.
  (message "Region appended to %s" (file-name-nondirectory filename)))

How to Use It:

  1. Evaluate the function definition.
  2. Write some text in a buffer, for example: Here is an idea for a story. And here is another one.
  3. Select the first sentence with the mark (C-SPC) and point.
  4. Run M-x ar-archive-region-to-file.
  5. At the prompt, enter a filename like ~/my-ideas.txt.
  6. The selected text will disappear from your buffer, and a confirmation message will appear. If you now open ~/my-ideas.txt, you will find your sentence there. If you repeat the process with the second sentence, it will be appended to the same file.

4. Case Study: A Complete File Processing Workflow

The System Crafters video series builds a complete tool to manage a .gitignore file. This serves as an excellent case study that combines everything we've discussed: finding/opening a file, searching its contents, deleting a section, inserting new text, and saving the result.

Now that you understand the individual file I/O functions, reviewing this example will solidify your understanding of how they fit together in a complete workflow.

Final Code for `.gitignore` updater

The following code from the System Crafters article Reading and Writing Buffers In Practice implements the full .gitignore update logic. Review it to see how the functions we've learned are orchestrated.

Read through the dotcrafter--update-gitignore function. Identify the lines that perform the key file I/O operations: find-file-noselect to get the buffer, and save-buffer to write the changes. Note how these bookend the buffer manipulation logic (search-forward, delete-region, insert).

This example demonstrates a robust pattern: load, modify, save. It's a pattern you will use frequently as you build your own custom Elisp functions.


Conclusion

You have now learned the essential functions for programmatic file input and output in Elisp. This skill is the bridge between manipulating text inside Emacs and automating tasks across your entire file system, a critical component of your goal to build a personalized workflow.

Key Takeaways:

  • Reading Files: Use find-file-noselect to visit a file in a new, hidden buffer. Use insert-file-contents to pull a file's content into the current buffer.
  • Writing Files: Use save-buffer to write a file-backed buffer's contents back to its associated file. Use write-region to write a specific part of a buffer to any file, with the option to append.
  • Common Workflows: The (or (get-file-buffer...) (find-file-noselect...)) idiom is a robust way to get a buffer for a file. The "load, modify, save" cycle is a common pattern for automated file processing.

Preview of the Next Lesson

When you use find-file-noselect, you create a full-fledged buffer that hangs around until you explicitly kill it. For quick, one-off manipulations, this can be overkill. In our next lesson, "Create temporary buffers using with-temp-buffer for safe text manipulation", we will learn a cleaner and safer way to perform operations. You'll see how to create a temporary, invisible workspace to perform complex text processing without affecting the user's visible buffers.