Let’s say you want to redirect the STDOUT of some command to a file (a config file for example) using the (bash) shell. In ideal case destination file should not be touched if it already has exactly the same content you are going to write into it. How to do that in a single pipe?
Here’s how:
echo "New contents" | diff -duaN "$target_path" - | patch --binary -s -p0 "$target_path"
#!/usr/bin/env bash
set -eu -o pipefail
to_stderr () {
>&2 cat
}
printable_only () {
tr -cd '\11\12\15\40-\176'
}
pipe_debug () {
tee >(printable_only | to_stderr)
}
to_file () {
local target_path="$1"
local restore_pipefail
# diff will return non-zero exit code if file differs, therefore
# pipefail shell attribute should be disabled for this
# special case
restore_pipefail=$(shopt -p -o pipefail)
set +o pipefail
diff -duaN "$target_path" - | pipe_debug | patch --binary -s -p0 "$target_path"
eval "$restore_pipefail"
}
md5 () {
md5sum -b | cut -f 1 -d ' '
}
sample_binary_data () {
local i
for (( i=0; i<=255; i++ )); do
printf "\x$(printf %x "$i")"
done
}
sample_text_data () {
cat <<EOF
Here be dragons
EOF
}
testfile='./testfile'
echo "Binary data MD5 $(sample_binary_data | md5)"
echo "Text data MD5: $(sample_text_data | md5)"
sample_text_data >"$testfile"
echo "$testfile is going to be modified"
sample_binary_data | to_file "$testfile"
echo "$testfile is NOT going to be modified"
sample_binary_data | to_file "$testfile"
echo "$testfile MD5: $(md5 <"$testfile")"
echo "$testfile is going to be modified"
sample_text_data | to_file "$testfile"
echo "$testfile is NOT going to be modified"
sample_text_data | to_file "$testfile"
echo "$testfile MD5: $(md5 <"$testfile")"
Sample output
Binary data MD5 e2c865db4162bed963bfaa9ef6ac18f0
Text data MD5: 890923a3ff411987e645531cc33548f6
./testfile is going to be modified
--- ./testfile 2017-07-07 07:21:39.853604436 +0100
+++ - 2017-07-07 07:21:39.856596473 +0100
@@ -1 +1,2 @@
-Here be dragons
+
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
\ No newline at end of file
./testfile is NOT going to be modified
./testfile MD5: e2c865db4162bed963bfaa9ef6ac18f0
./testfile is going to be modified
--- ./testfile 2017-07-07 07:21:39.994605593 +0100
+++ - 2017-07-07 07:21:40.146792083 +0100
@@ -1,2 +1 @@
-
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
\ No newline at end of file
+Here be dragons
./testfile is NOT going to be modified
./testfile MD5: 890923a3ff411987e645531cc33548f6