5

У меня есть файл, разделенный пробелами, длиной около 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 или нахождении альтернативного способа сделать то же самое.

Я могу легко преобразовать файл с разделителями-пробелами в файл с разделителями-запятыми, если это облегчит задачу.

Дайте мне знать, если требуется какое-либо разъяснение.

3 ответа3

6

Это предотвращает необходимость читать каждый файл много раз. Он читает каждый файл только один раз.

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 (или оболочкой). Часть оригинальной команды в верхней части, описываемая строками комментариев, представляет собой сценарий AWK. AWK сам по себе является языком программирования и не зависит от оболочки.

3

Есть много способов сделать это.

Вот способ, использующий только 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";
1

Это было бы хорошим приложением для awk . В качестве простого примера:

for variable in $(word.list.file)
do   
    awk -v pat=$variable '$5 ~ pat {$1 = "X"}1' file1 > tmp
    mv tmp > file1
done

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .