![我是否正确使用 sed 来执行此条件替换? 有没有更好的方法?](https://linux22.com/image/1264720/%E6%88%91%E6%98%AF%E5%90%A6%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8%20sed%20%E6%9D%A5%E6%89%A7%E8%A1%8C%E6%AD%A4%E6%9D%A1%E4%BB%B6%E6%9B%BF%E6%8D%A2%EF%BC%9F%20%E6%9C%89%E6%B2%A1%E6%9C%89%E6%9B%B4%E5%A5%BD%E7%9A%84%E6%96%B9%E6%B3%95%EF%BC%9F.png)
我有一个空格分隔的文件,大约有 3200 行。每行包含 7 个以上的字段。
我想要做的是编辑文件,sed
以便字段 5 中包含某个变量的每一行都将其字段 1 更改为 X。
我正在考虑做类似的事情:
for variable in `cat word.list.file`
do
sed 's/line_with_$variable_in_field5/replace_field1_with_X/g' old.file > new.file
cp new.file old.file
done
这是正确的吗?还有更好的方法吗?
我需要的帮助是填写sed
命令或者找到另一种方法来完成同样的事情。
如果这样可以使事情变得更容易,我可以轻松地将空格分隔的文件转换为逗号分隔的文件。
如果需要任何澄清,请告诉我。
答案1
这样就避免了多次读取每个文件。它只读取每个文件一次。
awk 'NR == FNR {a[$1]=1;next} $5 in a {$1="XYZ"} {print}' word.list.file old.file > new.file && mv new.file old.file
解释:
# if the current record number is the same as the record number in the file
# which means "if we're reading the first file"
NR == FNR {
a[$1]=1 # put a flag in an array indexed by the contents of the first field
next # read the next line in the file and continue at the top of the script
}
# Now we're processing the second file
# if field 5 exists as an index in the array named "a" (it's a word from the first file)
$5 in a {
$1="XYZ" # replace the first field with new contents
}
# for all lines in the second file, changed or not
{
print # print them
}' \
word.list.file old.file \
> new.file && \
mv new.file old.file
使用文件“word.list.file”和“old.file”作为输入。将输出写入“new.file”。如果整个操作没有产生错误(&&
),则将“new.file”重命名为“old.file”。本段中描述的部分是整个操作中唯一属于 Bash(或 shell)的部分。原始命令顶部和注释行描述的部分是 AWK 脚本。AWK 是一种独立的编程语言,独立于 shell。
答案2
有很多方法可以做到这一点。
以下是仅使用的方法bash
:
#!/bin/bash
# read word.list.file into words
words=$(<word.list.file)
# read line-by-line, each space-separated field goes into an array called fields
while IFS=$' \n' read -r -a fields; do
# could possibly be an associative array to make it faster
for word in $words; do
# zero-indexed, so 4 means the fifth field
if test "${fields[4]}" = "$word"; then
# change the first field to "X"
fields[0]="X"
fi
done
echo "${fields[*]}"
done <old.file >new.file
mv new.file old.file
这是一个使用的解决方案sed
:
#!/bin/bash
# bash-only syntax: read word.list.file into an array...
words=( $(<word.list.file) )
OIFS="$IFS"
IFS=$'|'
# ...and make a variable called "wordpattern"
# that contains a sed extended regular expression that matches
# any of those words, i.e. "word1|word2|word3..."
wordpattern="${words[*]}"
IFS="$OIFS"
# sed -r makes sed use extended re, which makes the pattern easier to read,
# but might only work on GNU/Linux and FreeBSD systems
# /...$wordpattern/ matches four words followed by a fifth word from word.list.file
# then the s/.../.../ makes a replacement on only those lines
# note that we have to use double quotes rather than single quotes
# so the shell can expand $wordpattern
sed -r -e "/^([^ ]* ){4}$wordpattern\>/s/^([^ ]*)(.*)/X\2/" old.file >new.file
mv new.file old.file
还有一个用(生锈的)Perl 编写的版本:
#!/usr/bin/env perl
my $wordfile = "word.list.file";
open WORDS, "<$wordfile"
or die "Cannot open $wordfile: $!\n";
my @words;
while (my $word = <WORDS>) {
chomp $word;
push @words, $word;
}
my $wordpattern = join '|', @words;
close WORDS;
my $oldfile = "old.file";
open IN, "<$oldfile"
or die "Cannot open $oldfile: $!\n";
my $newfile = "new.file";
open OUT, ">$newfile"
or die "Cannot open $newfile for writing: $!\n";
# output now goes to the OUT file handle (meaning $newfile) by default
select OUT;
while (my $line = <IN>) {
chomp $line;
my @fields = split / /, $line;
if ($fields[4] =~ /$wordpattern/) {
$fields[0] = "X";
}
$line = join ' ', @fields;
print $line . "\n";
}
close OUT;
close IN;
rename $newfile, $oldfile
or die "Cannot rename $newfile to $oldfile: $!\n";
答案3
这将是一个很好的应用awk
。举一个简单的例子:
for variable in $(word.list.file)
do
awk -v pat=$variable '$5 ~ pat {$1 = "X"}1' file1 > tmp
mv tmp > file1
done