1

Emacs 26.1, Windows 10, Dired+

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

a_subscriber
  • 4,062
  • 1
  • 18
  • 56

6 Answers6

4

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.

whatacold
  • 880
  • 6
  • 10
4

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"))))
jagrg
  • 3,914
  • 5
  • 19
  • In folder has 50 text files. Every file with size 5MB. Is this function "append-file-contents" success process all this files? – a_subscriber Feb 09 '19 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. – a_subscriber Feb 09 '19 at 13:25
  • 1
    I updated the answer. See if that works for you. Maybe try with some test files first. – jagrg Feb 09 '19 at 14:49
  • Would you like to re-indent the code so that it'll be more readable? – whatacold Feb 10 '19 at 05:05
  • @jagrg is it possible to append text of all files not only in current dir, but also in all subfolders? – a_subscriber Mar 19 '19 at 15:50
  • @Alexei try with directory-files-recursively instead of directory-files and see if that works. – jagrg Mar 19 '19 at 22:02
  • @jagrg directory-files-recursively: Wrong type argument: stringp, nil – a_subscriber Mar 20 '19 at 08:15
  • @Alexei the arguments are not the same, so change it to (directory-files-recursively dir "."). Also, the line (default-directory dir) is probably not needed. – jagrg Mar 20 '19 at 12:11
  • @jagrg (files (directory-files-recursively dir "." nil "^\([^#.~]\)"))) -> result: let: Wrong number of arguments: (2 . 3), 4 – a_subscriber Mar 20 '19 at 13:30
  • 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 '19 at 13:53
  • @jagrg (files (directory-files-recursively dir "." nil))) - now it' s work. I have one more question. Function "append-file-contents" can append ONLY TEXT files. It's not work with images (e.g. png). Is I'm right? – a_subscriber Mar 24 '19 at 16:12
  • You're right. Text files only. Maybe I should edit the function to make it more general. If you're wanting to append image files as well you could probably do it with Org as image links. – jagrg Mar 25 '19 at 10:46
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.

Tobias
  • 33,167
  • 1
  • 37
  • 77
  • 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 08 '19 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 08 '19 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 08 '19 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 08 '19 at 12:33
  • There is dired-get-marked-files in dired.el in Emacs 26.1, at least. dired+.el provides another one replacing it, see https://github.com/emacsmirror/dired-plus/blob/master/dired%2B.el#L2498 – whatacold Feb 08 '19 at 12:41
  • @whatacold My bad. Just rolled back the last changes. – Tobias Feb 08 '19 at 12:44
  • @Tobias 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 – a_subscriber Feb 08 '19 at 13:44
  • 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 08 '19 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.
Tobias
  • 33,167
  • 1
  • 37
  • 77
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.

Lorem Ipsum
  • 4,477
  • 2
  • 15
  • 35
  • 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 – a_subscriber Feb 08 '19 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. https://stackoverflow.com/questions/60244/is-there-replacement-for-cat-on-windows – Lorem Ipsum Feb 08 '19 at 13:48
  • 1
    I has Git. As result I has cat on my Windows machine. – a_subscriber Feb 08 '19 at 13:48
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.

Lorem Ipsum
  • 4,477
  • 2
  • 15
  • 35