是否有标准的替代海绵来将文件传输到自身中?

是否有标准的替代海绵来将文件传输到自身中?

我经常想做这样的事情:

 cat file | command > file

(这显然不起作用)。我见过的唯一解决方案是sponge,即

 cat file | command | sponge file

不幸的是,sponge我无法使用它(我也无法安装它或任何其他软件包)。

有没有更标准的快速方法来执行此操作,而不必每次都将其分解为多个命令(管道到临时文件,管道回到原始文件,删除临时文件)?tee例如,我尝试过,它似乎有效,但它是一个一致/安全的解决方案吗?

答案1

一个 shell 函数替换sponge

mysponge () (
    append=false

    while getopts 'a' opt; do
        case $opt in
            a) append=true ;;
            *) echo error; exit 1
        esac
    done
    shift "$(( OPTIND - 1 ))"

    outfile=$1

    tmpfile=$(mktemp "$(dirname "$outfile")/tmp-sponge.XXXXXXXX") &&
    cat >"$tmpfile" &&
    if "$append"; then
        cat "$tmpfile" >>"$outfile"
    else
        if [ -f "$outfile" ]; then
            chmod --reference="$outfile" "$tmpfile"
        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"
)

myspongeshell 函数将标准输入上的所有可用数据传递到临时文件。

当所有数据都已重定向到临时文件时,收集的数据将复制到由函数参数命名的文件中。如果数据不附加的到文件(即-a未使用),如果给定的输出文件名引用现有的常规文件,如果它不存在,则完成此操作mv(如果该文件是现有的常规文件,则尝试是首先使用 GNU 将文件模式传输到临时文件chmod)。如果输出不是常规文件(命名管道、标准输出等),则数据将以cat.

如果命令行上没有给出文件,则收集的数据将发送到标准输出。

最后,临时文件被删除。

函数中的每一步都依赖于上一步的成功完成。如果一个命令失败(它可能包含重要数据),则不会尝试删除临时文件。

如果指定的文件不存在,则将使用用户的默认权限等创建该文件,并将从标准输入到达的数据写入其中。

mktemp实用程序不是标准的,但它是普遍可用的。

上述函数模仿了中描述的行为的手册sponge来自moreutilsDebian 上的软件包。


使用代替tee不是sponge一个可行的选择。你说你已经尝试过并且它似乎对你有用。它可能有效,也可能无效。它依赖于管道中命令启动的时间(它们几乎同时启动)以及输入数据文件的大小。

以下是一个示例,显示使用tee不起作用的情况。

原始文件为 200000 字节,但经过管道后,它被截断为 32 KiB(这很可能对应于我系统上的某些缓冲区大小)。

$ yes | head -n 100000 >hello
$ ls -l hello
-rw-r--r--  1 kk  wheel  200000 Jan 10 09:45 hello
$ cat hello | tee hello >/dev/null
$ ls -l hello
-rw-r--r--  1 kk  wheel  32768 Jan 10 09:46 hello

答案2

有这个简短的 bash 脚本,需要 Perl
https://github.com/ildar-shaimordanov/perl-utils#sponge

第二个脚本应该是 moreutils 中版本的直接替代品

还有一个版本是独立的 Perl 脚本。

答案3

function wf() {
    #create a temporary file
    local tmpf="${1}_$(< /dev/urandom tr -dc A-Za-z0-9 | head -c16)"
    #redirect the result
    cat > $tmpf
    #replace the original file
    mv -f $tmpf "${1}"
}

接下来我们使用该函数

grep "error" messages.log | wf messages.log

答案4

为什么要用大炮来打苍蝇?一个可能的解决方案是这样的:

stdin-to-file () {
local function_name="${FUNCNAME[0]}"
local tmp_file
local append=false
local exit_code=0
for (( i=1; i<=$#; i++ )); do
    if [[ ${!i} = -- ]]; then
        set -- "${@:1:i-1}" "${@:i+1}"
        break
    fi
    if [[ ${!i} = -a || ( --append = ${!i}* && $(expr length "${!i}") -ge 3 ) ]]; then
        append=true
        set -- "${@:1:i-1}" "${@:i+1}"
        ((i--))
        continue
    fi
done
if [[ $# -ne 1 || -t 0 ]]; then
    echo "$function_name: Wrong number of arguments or missing stdin." >&2
    return 1
fi
tmp_file="$(mktemp "/tmp/$(basename -- "$1")-XXXXXXXXXXXX")" &&
cat > "$tmp_file" &&
if $append; then
    cat "$tmp_file" >> "$1"
else
    cat "$tmp_file" > "$1"
fi ||
exit_code=$?
rm -f -- "$tmp_file"
if [[ $exit_code != 0 ]]; then
    echo "$function_name: An error has occurred." >&2
fi
return $exit_code
}

然后:

cat file | command | stdin-to-file file

对于附加:

cat file | command | stdin-to-file -a file

或者:

cat file | command | stdin-to-file --append file

相关内容