Desk Clarity

A field guide to everyday digital problems.

Developer tools / Practical guide

Bash Command Substitution Removes Final Newlines: Preserve Bytes in a File

Quoting a command substitution preserves embedded newlines from word splitting, but it does not restore final newlines removed by the substitution itself.

By ON Lab2 min readSources checked November 10, 2026 (KST)

Quoting a command substitution preserves embedded newlines from word splitting, but it does not restore final newlines removed by the substitution itself. Use a file or direct stream when the exact ending bytes matter.

Reproduce in an empty temporary directory

This isolated example creates only fixture data in its working directory or runs local JavaScript. Run it separately from your project so the named files cannot overwrite real work.

printf 'alpha\n\n' > original.txt
captured=$(cat original.txt)
printf '%s' "$captured" > captured.txt
printf 'original bytes: '; wc -c < original.txt
printf 'captured bytes: '; wc -c < captured.txt
cat original.txt > streamed.txt
cmp original.txt streamed.txt
test "$(wc -c < captured.txt | tr -d ' ')" = 5

Observed local output

original bytes:        7
captured bytes:        5

What to compare

The fixture writes seven bytes, captures five, and preserves all seven through the direct file copy. Both ending newline characters disappear from the variable. This is different from losing line breaks because an unquoted expansion was split into words. Inspect a byte count or a hex dump instead of judging the result by the terminal prompt position.

Choose the next action

Keep exact document, checksum input or generated file content in a file and pass the filename to the next tool. If the consumer reads standard input, connect the stream directly rather than storing it in a shell variable first. If you deliberately want one logical text value without record terminators, command substitution is useful; document that contract and print with an explicit format. Adding one newline later is only correct if the contract requires exactly one, not an unknown number.

Limits and verification

Bash variables are not a general binary container and cannot represent embedded NUL bytes. A sentinel workaround for trailing newlines still needs deliberate status handling and does not solve that binary limitation. This isolated fixture demonstrates the ending-byte boundary with Bash on macOS; it does not validate a remote command or transport.

Related: Bash Read Skips the Last Line: Preserve a Partial Record at EOF.

Isolated example executed on macOS on November 10, 2026 (KST), using Bash 3.2, Python 3 for the byte fixture, and Node v26.5.0. Results describe the example, not your application or every runtime version.

Sources & further reading