1

I'm trying to write a script that ensures all text files end with a newline character.

When I run this command in a particular folder:

find . -exec ed -s {} <<< w \;

I get the following output:

Newline appended
Newline appended
Newline appended
Newline appended
Newline appended
Newline appended
Newline appended
Newline appended
Newline appended
Newline appended
Newline appended

Ok, but which files were the newlines appended to? I can't see any changes in the Git history, so are these untracked files?

I also deliberately removed the newline from the end of one of my text files, and ed isn't adding it back...

  • 1
    You shouldn't be using ed, which is loading and rewriting the whole file, making a copy of it. Instead, just check if the file ends in a newline with tail -c, and if not, append a newline: find . -type f -exec sh -c 'for a do [ "$(tail -c -1 "$a")" ] && { echo >>"$a" ; echo "+++ $a"; }; done' sh {} + –  Mar 19 '20 at 22:15
  • And you should stage and commit you changes if you want to see them in the git history ;-) –  Mar 19 '20 at 22:20
  • isn't adding it back – Because <<< w affects find. Every ed uses the same stdin as find. The very first ed consumes w\n and then the stream is empty for any other ed. – Kamil Maciorowski Mar 19 '20 at 22:26
  • Oh, I see. I got ed -s filename <<< w from somewhere on this website and I assumed that I could just place it into a find command. – Aaron Franke Mar 19 '20 at 22:27
  • Compare this answer. It's about < but the issue is the same. – Kamil Maciorowski Mar 19 '20 at 22:30

1 Answers1

2

Your command is slightly wrong as <<< is an input redirection (here-string) that is attached to find, not to ed.

To expand slightly on your command:

find . -type f -exec sh -c '
    for pathname do
        echo w | ed -s "$pathname"
    done' sh {} +

This would only affect regular files in the current directory or anywhere below it, and would not try to edit directories and other filetypes with ed. Note that binary files are also regular files, so we must assume that you only run this in directories containing text files (as to not damage compiled executables, image files, etc.)

For batches of regular files, this would call a short in-line shell script. The script would loop over the pathnames given to it by find and would open each in ed, only to immediately save the file. This adds the newline to files that does not already end with a newline character.

To get a hint about what files are actually modified by this, print out the pathname before calling ed. This would output all found pathnames, but the list would be interspersed by "Newline added" messages after some of them.

find . -type f -exec sh -c '
    for pathname do
        printf "%s\n" "$pathname"
        echo w | ed -s "$pathname"
    done' sh {} +

To detect which files were actually modified by this, you could write to a temporary file, compare that with the original file, and replace the original with the temporary file if there is a difference.

find . -type f -exec sh -c '
    tmpfile=$(mktemp); trap "rm -f \"$tmpfile\"" EXIT
    for pathname do
        printf "w %s\n" "$tmpfile" | ed -s "$pathname" 2>/dev/null
        if ! cmp -s "$tmpfile" "$pathname"; then
            printf "Fixed %s\n" "$pathname"
            mv "$tmpfile" "$pathname"
        fi
    done' sh {} +

This has the added benefit that you won't be updating timestamps on files other than the ones that you actually modify.

This also suppresses the "Newline added" message from ed and instead outputs a custom message whenever a file has actually been modified.

See also:

Kusalananda
  • 333,661
  • Thank you for your answer, but I already solved it in another way. See here for my solution: https://github.com/godotengine/godot-demo-projects/blob/master/file_format.sh – Aaron Franke Dec 17 '20 at 05:46