From a67fa4f250666124d5dd8630bfa667b35c43cdde Mon Sep 17 00:00:00 2001 From: Andrew Dunham Date: Tue, 13 Jul 2021 19:04:49 -0400 Subject: [PATCH] lib: add user_homedir and sponge functions --- lib | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ test/lib_test.sh | 43 +++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/lib b/lib index b93ab6a..a5d50ca 100755 --- a/lib +++ b/lib @@ -80,3 +80,62 @@ epochseconds() { # TODO(andrew-d): break down and verify the math here echo $(( $(TZ=GMT0 date +"((%Y-1600)*365+(%Y-1600)/4-(%Y-1600)/100+(%Y-1600)/400+1%j-1000-135140)*86400+(1%H-100)*3600+(1%M-100)*60+(1%S-100)") )) } + +# user_homedir returns the home directory for the provided user +user_homedir() { + local user="$1" + + awk \ + -v u="$user" \ + -v FS=':' \ + '$1==u {print $6}' \ + /etc/passwd +} + +# sponge acts like sponge(1) from moreutils, but is portable. +# +# It "soaks up" all input from stdin before writing it to the specified output; +# helpful for modifying a file in-place with a utility that does not support +# it. +sponge() { + local append=false + + while getopts 'a' opt; do + case $opt in + a) append=true ;; + *) echo "unknown option: $opt" >&2; exit 1;; + esac + done + shift "$(( OPTIND - 1 ))" + + local outfile="$1" + local tmpfile + + tmpfile="$(mktemp "$(dirname "$outfile")/tmp-sponge.XXXXXXXX")" && + cat >"$tmpfile" && + if "$append"; then + cat "$tmpfile" >>"$outfile" + else + if [ -f "$outfile" ]; then + # NOTE: the stat() call here isn't exactly portable, but both GNU + # coreutils and busybox support it, so close enough? Let's + # double-check that it returns something sensible just to be sure. + local oldperms + oldperms="$(stat -c '%a' "$outfile")" + + if [ "${#oldperms}" = "3" ] && [ -z "$(echo "$oldperms" | tr -d '0-9')" ]; then + chmod "$oldperms" "$tmpfile" + else + printf "warning (bad: %s): not setting permssions on: %s\n" "${oldperms}" "$outfile" >&2 + fi + fi + if [ -f "$outfile" ]; then + mv "$tmpfile" "$outfile" + elif [ -n "$outfile" ] && [ ! -e "$outfile" ]; then + cat "$tmpfile" >"$outfile" + else + cat "$tmpfile" + fi + fi && + rm -f "$tmpfile" +} diff --git a/test/lib_test.sh b/test/lib_test.sh index 872551d..e53ab11 100755 --- a/test/lib_test.sh +++ b/test/lib_test.sh @@ -95,5 +95,48 @@ testEpochseconds() { assertEquals "" "$(epochseconds | tr -d '[0-9]')" } +testUserHomedir() { + assertEquals "$HOME" "$(user_homedir "$USER")" +} + +testSponge() { + for i in $(seq 1 10000); do + echo "i am a test line of length 30" >> "$TEST_TMPDIR/largefile.txt" + echo "i am a neat line of length 30" >> "$TEST_TMPDIR/expected.txt" + done + + # Modify the file; if we don't sponge, this will probably overwrite things + # and won't work. + cat "$TEST_TMPDIR/largefile.txt" | \ + sed 's/test/neat/g' | \ + sponge "$TEST_TMPDIR/largefile.txt" + + # The files should be equal (currently, we just compare CRCs) + assertEquals "$(cksum < "$TEST_TMPDIR/largefile.txt")" "$(cksum < "$TEST_TMPDIR/expected.txt")" +} + +testSponge_Permissions() { + echo "i am the input file" > "$TEST_TMPDIR/input.txt" + echo "i am the output file" > "$TEST_TMPDIR/output.txt" + + # Set a strange permission on the output file that we will replicate. + chmod 0741 "$TEST_TMPDIR/output.txt" + + cat "$TEST_TMPDIR/input.txt" | sponge "$TEST_TMPDIR/output.txt" + + # The files should be equal + assertEquals "$(cat "$TEST_TMPDIR/input.txt")" "$(cat "$TEST_TMPDIR/output.txt")" + assertEquals "741" "$(stat -c '%a' "$TEST_TMPDIR/output.txt")" +} + +testSponge_Append() { + echo "one" > "$TEST_TMPDIR/input.txt" + echo "two" > "$TEST_TMPDIR/output.txt" + + cat "$TEST_TMPDIR/input.txt" | sponge -a "$TEST_TMPDIR/output.txt" + + assertEquals "$(printf 'two\none\n')" "$(cat "$TEST_TMPDIR/output.txt")" +} + # Load shUnit2 . ./shunit2