Альтернатива использованию sed внутри цикла - PullRequest
2 голосов
/ 11 апреля 2020

Я хочу заменить несколько (непоследовательных) строк большого файла, используя файлы, содержащие одну строку. То, что я нашел для работы:

for i in ${list[@]}; do
   line=$(cat file_$i.txt);
   sed -i "$i c $line" bigfile.txt;
done

Здесь list содержит строки, которые я хочу заменить. Вот пример:

$ list=(1 3 4 7)
$ cat file_1.txt
this is the new line 1
$ cat file_3.txt
this is the new line 3
$ cat file_4.txt
this is the new line 4
$ cat file_7.txt
this is the new line 7

$ cat bigfile.txt
line 1
line 2
line 3
line 4
line 5
line 6
line 7
line 8

Вывод приведенного выше сценария:

$ cat bigfile.txt 
newline 1
line 2
newline 3
newline 4
line 5
line 6
newline 7
line 8

Он работает, но на каждом шаге l oop sed открывает и читает Целый файл, насколько я понимаю, поэтому этот метод очень медленный. Каковы другие более быстрые способы сделать это, предпочтительно используя sed?

Ответы [ 3 ]

3 голосов
/ 11 апреля 2020

Возможное решение:

sed "$(for i in $list; do echo "$i c $(cat file_$i.txt)"; done)" bigfile.txt

($list может быть ${list[@]} или ${list[*]} или любым другим, в зависимости от того, как оно построено.)

Ваш оригинал l oop используется для построения сценария Sed, каждая строка которого похожа на 1 c content_of_file_1_dot_txt; этот скрипт запускается только один раз на bigfile.txt.

2 голосов
/ 12 апреля 2020

Забудьте о массиве list[] и запустите его в каталоге, содержащем только ваши файлы:

awk '
sub(/^file_/,"",FILENAME) { map[FILENAME+0] = $0; next }
{ print (FNR in map ? map[FNR] : $0) }
' file_*.txt bigfile.txt

или, если вы тоже используете массив list []:

awk -v list="${list[*]}" '
BEGIN {
    split(list,tmp)
    for (i in tmp) {
        lineNrs[tmp[i]]
    }
}
NR in lineNrs {
    if ( (getline line < ("file_" NR ".txt")) > 0 ) {
        $0 = line
    }
    close("file_" NR ".txt")
}
{ print }
' bigfile.txt
0 голосов
/ 12 апреля 2020

Это может сработать для вас (GNU sed и параллельно):

<<<"${list[@]}" sed -E 's/\S+/&r file_&.txt\n&d\n/g' | sed -i -f - bigfile

Или с использованием параллели GNU:

parallel 'echo "{}r file_{}.txt";echo "{}d"' ::: ${list[@]} | sed -i -f - bigfile

Или если текстовые файлы замены содержат только одну строку каждый:

parallel echo '{}c$(<file_{}.txt)' ::: ${list[@]} | sed -i -f - bigfile

Или вы можете использовать имена файлов в текущем каталоге:

parallel --rpl '{@} s/[^0-9]//g' 'echo "{@}r {}";echo "{@}d"' ::: file_* |
sed -i -f - bigfile
...