使用这种格式在命令行给出的文件中搜索单词的最简单方法是什么
./<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}' \;
基本上
grep -rn "1001" file1
会给出以下行:1001|text4|text5|text6
获得上述输出后,然后使用
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