>>>>> On Tue, 31 May 2022, Ionen Wolkens wrote: > +# @FUNCTION: esed > +# @USAGE: <sed-argument>... > +# @DESCRIPTION: > +# sed(1) wrapper that dies if the expression(s) did not modify any files. > +# sed's -i/--in-place is forced, and so stdin/out cannot be used.
This sounds like a simple enough task ... > +esed() { > + local -i i > + > + if [[ ${esedexps@a} =~ a ]]; then > + # expression must be before -- but after the rest for e.g. -E > to work > + local -i pos > + for ((pos=1; pos<=${#}; pos++)); do > + [[ ${!pos} == -- ]] && break > + done > + > + for ((i=0; i<${#esedexps[@]}; i++)); do > + [[ ${esedexps[i]} ]] && > + esedexps= esed "${@:1:pos-1}" -e > "${esedexps[i]}" "${@:pos}" > + done > + > + unset esedexps > + return 0 > + fi > + > + # Roughly attempt to find files in arguments by checking if it's a > + # readable file (aka s/// is not a file) and does not start with - > + # (unless after --), then store contents for comparing after sed. > + local contents=() endopts files=() > + for ((i=1; i<=${#}; i++)); do > + if [[ ${!i} == -- && ! -v endopts ]]; then > + endopts=1 > + elif [[ ${!i} =~ ^(-i|--in-place)$ && ! -v endopts ]]; then > + # detect rushed sed -i -> esed -i, -i also silently > breaks enewsed > + die "passing ${!i} to ${FUNCNAME[0]} is invalid" > + elif [[ ${!i} =~ ^(-f|--file)$ && ! -v endopts ]]; then > + i+=1 # ignore script files > + elif [[ ( ${!i} != -* || -v endopts ) && -f ${!i} && -r ${!i} > ]]; then > + files+=( "${!i}" ) > + > + # eval 2>/dev/null to silence \0 warnings if sed binary > files > + eval 'contents+=( "$(<"${!i}")" )' 2>/dev/null \ > + || die "failed to read: ${!i}" > + fi > + done > + (( ${#files[@]} )) || die "no readable files found from '${*}' > arguments" > + > + local verbose > + [[ ${ESED_VERBOSE} ]] && type diff &>/dev/null && verbose=1 > + > + local changed newcontents > + if [[ -v _esed_output ]]; then > + [[ -v verbose ]] && > + einfo "${FUNCNAME[0]}: sed ${*} > ${_esed_output} ..." > + > + sed "${@}" > "${_esed_output}" \ > + || die "failed to run: sed ${*} > ${_esed_output}" > + > + eval 'newcontents=$(<${_esed_output})' 2>/dev/null \ > + || die "failed to read: ${_esed_output}" > + > + local IFS=$'\n' # sed concats with newline even if none at EOF > + contents=${contents[*]} > + unset IFS > + > + [[ ${contents} != "${newcontents}" ]] && changed=1 > + > + [[ -v verbose ]] && > + diff -u --color --label="${files[*]}" > --label="${_esed_output}" \ > + <(echo "${contents}") <(echo "${newcontents}") > + else > + [[ -v verbose ]] && einfo "${FUNCNAME[0]}: sed -i ${*} ..." > + > + sed -i "${@}" || die "failed to run: sed -i ${*}" > + > + for ((i=0; i<${#files[@]}; i++)); do > + eval 'newcontents=$(<"${files[i]}")' 2>/dev/null \ > + || die "failed to read: ${files[i]}" > + > + if [[ ${contents[i]} != "${newcontents}" ]]; then > + changed=1 > + [[ -v verbose ]] || break > + fi > + > + [[ -v verbose ]] && > + diff -u --color --label="${files[i]}"{,} \ > + <(echo "${contents[i]}") <(echo > "${newcontents}") > + done > + fi > + > + [[ -v changed ]] \ > + || die "no-op: ${FUNCNAME[0]} ${*}${_esed_command:+ (from: > ${_esed_command})}" > +} ... but then it's almost 100 lines of shell code, including very convoluted parsing of parameters. The code for detection whether a parameter is actually a file is a heuristic at best and looks rather brittle. Also, don't use eval because it is evil. So IMHO this isn't the right approach to the problem. If anything, make it a simple function with well defined arguments, e.g. exactly one expression and exactly one file, and call "sed -i" on it. Then again, we had something similar in the past ("dosed" as a package manager command) but later banned it for good reason. Detection whether the file contents changed also seems complicated. Why not compute a hash of the file before and after sed operated on it? md5sum or even cksum should be good enough, since security isn't an issue here. Ulrich
signature.asc
Description: PGP signature