0

Emacs 26.1, Windows 10, Dired+

How to append contents of multiple files in one folder into one file?

2

Here's a function that appends the contents of each file under DIR and writes the results into a result.txt file in the same directory.

(defun append-file-contents (dir)
  (interactive "DDirectory: ")
  (let (contents
        (default-directory dir)
        (files (directory-files dir nil "^\\([^#.~]\\)")))
    (dolist (file files)
      (when (file-regular-p file)
        (push (with-temp-buffer
                (insert-file-contents file)
                (buffer-string))
              contents)))
    (write-region (mapconcat #'identity contents "\n")
                   nil (concat dir "result.txt"))))
  • In folder has 50 text files. Every file with size 5MB. Is this function "append-file-contents" success process all this files? – Alexei Feb 9 at 13:18
  • Can you modify your function to be "interactive" with param dir path? And store result to file,e.g. result.txt. Thanks. – Alexei Feb 9 at 13:25
  • 1
    I updated the answer. See if that works for you. Maybe try with some test files first. – jagrg Feb 9 at 14:49
  • Would you like to re-indent the code so that it'll be more readable? – whatacold Feb 10 at 5:05
  • 1
    You're exceeding the number of arguments. Remove the last one. I would also suggest enabling eldoc-mode. It shows you the arglist in the minibuffer. My other solution involves GNU find but I suspect it won't work with Windows. – jagrg Mar 20 at 13:53
2

One way to get it done is Shell Commands in Dired, together with cat:

  1. Open the directory in dired mode with C-x d
  2. Mark the desired files with m
  3. Run a shell command by hitting ! and typing the command: cat ? >> /path/to/accumulate-file.txt

    The ? surrounded with whitespaces means running the shell command once for each marked files, with ? being replaced by the file name.

  • Did you test it with emacs26.1 on Windows 10? Does it work on Windows? – Tobias Feb 8 at 11:15
  • I get error message: The process cannot access the file because it is being used by another process. – Alexei Feb 8 at 11:21
  • Haven't tested it on Windows yet :( I guess it should work if there is cat, it seems I'm wrong from OP's comment. I am trying to do it in elisp way. – whatacold Feb 8 at 11:34
  • I have added an Elsip solution. – Tobias Feb 8 at 11:55
  • @Alexei Since you have cat as you mentioned in other comment, then I don't understand why this error. Is there any other app "lock" the files? Does cat work in Git Bash? – whatacold Feb 8 at 14:04
1
  1. Open the buffer where you want to insert the files and put point where you want the text to be inserted.

  2. Find the directory with the files, i.e., C-x C-f. The directory is shown in a dired buffer.

  3. Mark the files with m.

    a. You can also mark with % m if you want to select the files by a regular expression.

    b. You can also use % g if you want to mark files containing matches for a given regular expression.

  4. The basic lisp code that provides you with the list of marked files is:

    (dired-get-marked-files)
    

    The files have the same order as in the dired buffer. You can also sort in other ways with cl-sort.

  5. Insert files from the list into your target buffer. Therefore run the following lisp code with the dired buffer current:

    (dolist (fn (dired-get-marked-files)) (with-current-buffer "target-buffer-name" (insert-file fn)))
    

    Note:

    a. You have to replace "target-buffer-name" by the name of your target buffer. Often that is the file name of your target file.

    b. For this command to work the buffer with name "target-buffer-name" must already exists.

    c. If you want to create a new buffer use (get-buffer-create "target-buffer-name") instead.

You can run the elisp expressions in the current buffer easily with the key sequence M-:. You just input the elisp expression in the minibuffer and run it by pressing the RET button.

  • 1
    The docstring of insert-file says: Don’t call it from programs! Use ‘insert-file-contents’ instead. , so maybe insert-file-contents is better? – whatacold Feb 8 at 12:11
  • (dired-get-marked-files) seem more intuitive to me to get the file lists, and it returns them as in the buffer top-down. – whatacold Feb 8 at 12:18
  • @whatacold Thanks I will replace (dired-map-over-marks ...) by (dired-get-marked-files). I know about insert-file-contents and opted for insert-files because it works and it is shorter. At this point one just needs the job to be done. – Tobias Feb 8 at 12:26
  • @whatacold Note that dired-get-marked-files is from dired+.el which is not necessarily installed. Therefore I will add my original solution again. – Tobias Feb 8 at 12:33
  • 1
    @Alexei You don't need dired-mode for that. Just use M-! if that works for you. The solution I offered is elisp only and works independently of the OS. – Tobias Feb 8 at 14:23
1

...And then there is the Eshell-way. It uses Elisp under the hood when needed. Therefore it is OS-independent:

  1. Open an Eshell via M-x eshell.

  2. Change directory in the Eshell to the one you want with cd YourDirectoryPath.

  3. Concatenate all files you want in Eshell with cat Pattern*Of*Source*Files > targetFile.

Note, emacs -Q uses Elisp for all above steps under Ubuntu (WSL):

~ $ which cd
eshell/cd is a compiled Lisp function in ‘em-dirs.el’.
~ $ which cat
eshell/cat is a compiled Lisp function in em-unix.el.
0

For completeness, as sometimes the crudest of methods is the most appropriate...

  1. Open the second file using C-x C-f (find-file)
  2. Select the entire contents using C-x h (mark-whole-buffer)
  3. Copy the text using M-w (kill-ring-save)
  4. Open the first file using C-x C-f (find-file)
  5. Move to the end of the buffer using M-> (end-of-buffer)
  6. Paste the text from the first file using C-y (yank)

At this point, you could save over the original first file using C-x C-s (save-file) or save a copy using C-x C-w (write-file). Repeat this as needed for all the files.

  • In folder I has 50 files. So I don't want to repeat 6 steps 50 times! – Alexei Feb 8 at 13:47
  • And why not!? ;) – Lorem Ipsum Feb 8 at 13:48
  • "mark-whole-buffer" The problem that this 50 files are large. So mark whole buffer is very slow and maybe not work correct. – Alexei Feb 8 at 13:49
  • By "cat" command I can add to one file content of all e.g. java files in current folder and all subfolders like this: find . -type f -name "*.java" -exec cat {} \; > result.txt – Alexei Mar 19 at 16:10
0

Open file1.txt using C-x C-f (find-file). Press M-! to execute shell-command. The command you want to execute is copy:

copy /b file1.txt + file2.txt + file2.txt file1.txt

Reload the buffer using M-x revert-buffer to see that indeed the files were appended. Beware! The copy command will overwrite file1.txt.

If you don't have file1.txt open, then the default-directory will not be for the correct folder. You would need to either issue cd first to change the default directory for the current buffer or use absolute paths.

  • 1
    In dired mode M-!. Then use command: cat trace* > result.txt . This command copy content of all file with names trace* to ONE file result.txt – Alexei Feb 8 at 13:43
  • I don't think that default Windows 10 has cat. If you have Gitbash installed, though, then you do and it's likely on the path. You could use type, though. stackoverflow.com/questions/60244/… – Lorem Ipsum Feb 8 at 13:48
  • 1
    I has Git. As result I has cat on my Windows machine. – Alexei Feb 8 at 13:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.