通过 shell 脚本更改文件的特定部分

通过 shell 脚本更改文件的特定部分

使用这种格式在命令行给出的文件中搜索单词的最简单方法是什么

./<file1> -f <file2> --edit <id> <column> <value>

我想搜索一个人<id>并更改给定的<file2>单词。<column><value>

我努力了

awk -F '|' -v ID="$4" -v Column="$5" \
           -v Value="$6" 'ID==$1 {$Column=Value ;}1' \
           OFS='|' $2>NewFile
mv NewFile $2 ;

但我希望在没有临时文件的情况下完成

例如:

1000|text1|text2|text3
1001|text4|text5|text6
1002|text7|text8|text9

我执行后

./<file> -f file2 --edit 1001 2 Marios

它应该有这样的变化:

1000|text1|text2|text3
1001|Marios|text5|text6
1002|text7|text8|text9

答案1

在没有临时文件的情况下编辑文本文件是一个坏主意,并且通常不会在 Unix 脚本中完成。它需要重写整个文件,或者至少重写受编辑影响的后缀部分。如果写入被中断,则文件已损坏。

当然,我们每天都使用文本编辑器执行此操作:我们将文件保存在内存中,并在保存时将它们覆盖在磁盘上。区别在于,所有像样的编辑器都至少保留一个备份,除非该功能被明确禁用(这是一个额外的文件,大概是您不可接受的),并且编辑器是交互式的:如果保存因任何原因失败(磁盘完整,系统崩溃,无论如何)一个人知道它。如果不是崩溃,编辑器仍在运行,并且尽管保存失败,文件仍保留在内存中。用户可以执行命令将文件保存在其他地方,或者在程序之外采取一些操作来修复某些情况后再次尝试保存。

TXR 中的解决方案:从内存中的副本进行覆盖,没有备份或恢复策略:

#!/usr/local/bin/txr --lisp

(defvarl myname [*args-full* 2])

;; check for required arguments syntax
(unless (and (= (length *args*) 6)
             (equal [*args* 0] "-f")
             (equal [*args* 2] "--edit"))
  (put-line `usage: @myname -f <file> --edit <col1-key> <col-num> <replace>`)
  (exit 1))

;; do in-memory update and overwrite
(let ((file [*args* 1])
      (key [*args* 3])
      (col (pred (tointz [*args* 4]))) ;; pred, because [f #] is zero based
      (val [*args* 5])
      (ss (make-strlist-output-stream))) ;; in-memory string list stream

  ;; awk into memory
  (awk (:inputs file) ;; input from file
       (:output ss) ;; output stream is in-memory string list
       (:set fs "|") ;; field separator is pipe
       ((equal [f 0] key) (set [f col] val)) ;; do replacement
       (t)) ;; true condition with no action -> default print action

  ;; overwrite original file with string list
  (with-stream (out (open-file file "w"))
    (put-lines (get-list-from-stream ss) out)))

会议:

$ diff -u data.orig data
$ ./inplace 
usage: ./inplace -f <file> --edit <col1-key> <col-num> <replace>
$ ./inplace -f data --edit 1001 2
usage: ./inplace -f <file> --edit <col1-key> <col-num> <replace>
$ ./inplace -f data --edit 1001 2 Marios
$ diff -u data.orig data
--- data.orig   2016-10-16 08:05:03.233736781 -0700
+++ data    2016-10-16 08:15:57.412394022 -0700
@@ -1,3 +1,3 @@
 1000|text1|text2|text3
-1001|text4|text5|text6
+1001 Marios text5 text6
 1002|text7|text8|text9

答案2

您似乎正在寻找的一件事是命令行解析。可以case在 POSIX shell 中使用 来完成不错的解析。

Next AWK 完全可以执行该转换。要就地执行此操作,您有两种选择:-i按照 John 的建议使用 GNU awk(带有 ),或者使用临时文件。下面是一个使用 的示例mktemp,尽管mktemp不是 POSIX它几乎存在于所有 *nix 系统中

#!/bin/sh

while test $# -gt 0
do
    case "$1" in
      -f)
      file="$2"
      shift
      shift
      ;;
      --edit)
      id="$2"
      column="$3"
      value="$4"
      shift
      shift
      shift
      shift
      ;;
      *)
      echo "Usage:"
      echo "  $0 -f <file> --edit <id> <column> <value>"
      exit
      ;;
  esac
done
# debug
echo "edit [$file] in [$id] change column [$column] to [$value]"

tmpf=`mktemp`
awk -v FS="|" -v OFS="|" "/^$id/ { \$$column = \"$value\" }1" "$file" > "$tmpf"
mv "$tmpf" "$file"

这个想法是在将程序传递给 awk 时转义正确的字符。假设调用上面的脚本,script.sh您可以简单地执行以下操作:

./script.sh -f myfile --edit 1001 3 "It's a me Mario"

这仍然存在一些问题,我在下面解决以不对其进行聚类。首先,您还应该检查参数数量是否为空:

if test $# -eq 0
then
    echo Usage
    exit
fi

其次,使用平原mv有时是危险的。特别是当出现问题并且脚本不产生输出时。在 an 周围添加这样的东西总是好的,mv这样会覆盖输入:

if test -s "$tmpf"
then
    mv "$tmpf" "$file"
else
    echo Something went wrong
fi

答案3

假设您有一个如下所示的文件,并且需要获取如上所述的输出:

输入文件:

1000|text1|text2|text3
1001|text4|text5|text6
1002|text7|text8|text9

输出应该是:

1000|text1|text2|text3
1001|Marios|text5|text6
1002|text7|text8|text9

请尝试一下:

grep -rn "1001" file1 | awk -F '|' '{export $2=<new value>;print $2}' \;
  1. 基本上grep -rn "1001" file1 会给出以下行:

    1001|text4|text5|text6

  2. 获得上述输出后,然后使用awk更改第二列的值(这是用“ |”分隔的字段),并打印该值。

我目前没有执行它的环境,但我确信该逻辑将对您想要实现的目标有所帮助。

结论:我建议不要在脚本中使用临时文件,因为如果我们使用更多的临时文件分配,服务器的性能将会下降,因为服务器中会发生更多的 I/O,进而降低其性能并使得服务器慢。

答案4

一些简单代码的单行 shell 函数包装器sed

# Usage: foo <file2> <id> <column> <value>
foo() { sed -i "/^$2|/s/[^|]*/$4/$3" "$1" ; }

例子:

 foo file2 1001 2 Marios  ;  cat file2

输出:

1000|text1|text2|text3
1001|Marios|text5|text6
1002|text7|text8|text9

相关内容