Цикл bash «при чтении строки» с переменными в качестве ввода файла

Пример кода и идея следующим образом

while read url; do
    wget -q $url -O - | grep -o -E 'href = "([^"#]+)"' | grep "magazine/" | grep "https" | sort -u | sed -r 's/.*href = "([^"]+).*/\1/g' >> list1
    perl -ne 'print unless $dup{$_}++;' list1 > list
done < list

Начальная строка списка — https://abc.xyz/issues/, отсюда wget хочет найти одну конкретную URL-ссылку на предыдущую «проблему» точного формата https://abc.xyz/issues/yyyy/mm/dd (фильтровать с помощью grep, удалять дубликаты по сортировке и извлекать URL-ссылку с помощью sed) , затем добавьте URL-ссылку в «список», такая URL-ссылка затем будет использоваться для получения следующей URL-ссылки в цикле «при чтении строки»... и строка Perl хочет удалить дубликаты в списке после появления новой URL-ссылки (s) добавлены в список перед обработкой новой URL-ссылки в цикле.

Такова идея, и идеальным результатом должен быть список сотен ссылок на все прошлые выпуски. Был бы признателен за некоторые предложения или, что лучше, простое решение (у меня есть очень базовые знания команд оболочки)

Я не уверен, в чем вопрос. Это работает или нет?

Barmar 11.05.2024 01:49

Если это не сработает, каков ожидаемый результат и что вы получите взамен?

Barmar 11.05.2024 01:49

Кажется, что команда perl просто удаляет дубликаты. Разве вы еще не сделали это с sort -u?

Barmar 11.05.2024 01:50

У вас должна быть возможность объединить все команды: grep -o -E 'href = "https:[^"#]+magazine/[^"#]+"'

Barmar 11.05.2024 01:53

@Barmar, это не работает... его запуск немедленно возвращает обратно к приглашению, и без какой-либо новой ссылки в списке... после фильтра grep строка wget иногда все еще возвращает несколько URL-ссылок, а команда perl попробуйте удалить такие дубликаты... спасибо за предложение grep, попробую позже

cimba8 11.05.2024 03:44

@Barmar, командная строка работает как для wget, так и для perl, если запустить ее в оболочке; но цикл не работает... во входной <список будет добавлена ​​новая URL-ссылка после каждого цикла, а затем передана команде wget для повторения процесса, чего еще нет

cimba8 11.05.2024 03:54
perl -E 'while($u=shift@ARGV){ push @ARGV, grep{!$seen{$_}++ && say} qx{wget -qO- \Q$u\E} =~ m{href = "(https:[^"#]*magazine/[^"#]*)"}g; }' "$url" >list ?
jhnc 11.05.2024 08:57

@jhnc эта однострочная команда работает! он останавливается на определенной ссылке после 150+, и оказывается, что на этой конкретной веб-странице отсутствует ссылка href «предыдущая проблема» ... Я плохо представляю, как работает эта однострочная команда, и я хотел бы понять структуру, если вам хочется комментировать:)

cimba8 11.05.2024 09:27

@jhnc при повторном использовании приведенной выше команды из ссылки «стоп» я могу вручную указать недостающую ссылку URL, тогда возможно ли, чтобы она следовала существующему «направлению» и получала только ссылку «предыдущая проблема», но не те, которые уже в списке? т.е. /гггг/мм/дд в предыдущих ссылках всегда старше текущей ссылки

cimba8 11.05.2024 09:57

на самом деле я не уверен, что мой код работает правильно, поскольку magazine/ не отображается в формате URL, который вы указываете (https://abc.xyz/issues/yyyy/mm/dd), поэтому кажется, что вы ищете его где-то кроме значения href. Вам необходимо предоставить образец HTML-кода из wget.

jhnc 11.05.2024 17:36

Разве не должно быть wget -q "$url" ...` ? Кроме того, рекомендуется скопировать/вставить свой код в shellcheck.net и исправить все отмеченные проблемы. Обязательно включите #!/bin/bash (или другой) в качестве первой строки сценария.

shellter 11.05.2024 18:15

@jhnc извините за это, мой образец исследует веб-сайт New Yorker "newyorker.com/magazine", их еженедельные выпуски фактически следуют строгому формату даты +7 в /гггг/мм/дд с 1925 года.

cimba8 12.05.2024 04:39
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
13
113
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

С:

while read url; do
    ...
    perl ... list1 > list
done < list

Вы пишете в тот же файл, что и читаете, list, не делайте этого. Изменять

    perl -ne 'print unless $dup{$_}++;' list1 > list
done < list

к

    perl -ne 'print unless $dup{$_}++;' list1 > outfile
done < list

или лучше:

    perl -ne 'print unless $dup{$_}++;' list1
done < list > outfile

чтобы избежать ЭТОЙ серьезной проблемы, но тогда у вас возникнут и другие проблемы, включая эту:

wget ... >> list1
perl -ne 'print unless $dup{$_}++;' list1

который постоянно добавляется к list1, поэтому perl приходится заново обрабатывать предыдущее содержимое на каждой итерации цикла. Вероятно, вам следует сделать:

wget ... > list1
perl -ne 'print unless $dup{$_}++;' list1

вместо этого заново заполнять list1 на каждой итерации вместо добавления к нему, но я не знаю, почему вы вообще используете там файл, а не просто передаете его по конвейеру perl:

wget ... |
perl -ne 'print unless $dup{$_}++;'

В целом, это может быть то, что вы пытаетесь сделать (непроверено и не рекомендуется, просто пытаетесь заставить код OP функционировать):

#!/usr/bin/env bash

while IFS= read -r url; do
    wget -q "$url" -O - |
    grep -o -E 'href = "([^"#]+)"' |
    grep "magazine/" |
    grep "https" |
    sort -u |
    sed -r 's/.*href = "([^"]+).*/\1/g' |
    perl -ne 'print unless $dup{$_}++;' 
done < list > outfile

Без примера ввода (вывод из curl) и ожидаемого вывода (из цикла) это всего лишь догадки, но приведенное выше, вероятно, можно было бы изменить на это, чтобы не создавать подоболочки повторно для вызова всех этих команд внутри цикла:

while IFS= read -r url; do
    wget -q "$url" -O -
done < list |
    grep -o -E 'href = "([^"#]+)"' |
    grep "magazine/" |
    grep "https" |
    sort -u |
    sed -r 's/.*href = "([^"]+).*/\1/g' |
    perl -ne 'print unless $dup{$_}++;' \
        > outfile

и далее я ожидаю, что этот конвейер из 6 команд:

grep -o -E 'href = "([^"#]+)"' |
grep "magazine/" |
grep "https" |
sort -u |
sed -r 's/.*href = "([^"]+).*/\1/g' |
perl -ne 'print unless $dup{$_}++;' 

можно заменить одной командой awk (или perl).

Большой! это действительно то, что мне нужно прокормить себя :) недавно упал в эту кроличью нору bash... в моем примере кода, когда я запускаю строку wget, за которой следует строка perl, и получаю новую ссылку URL, я вручную копирую ее в $url и повторите процесс, и я смогу получить ожидаемые URL-ссылки, но не знаю, как передать новый URL-адрес «способом bash» :) это очень помогает!

cimba8 12.05.2024 04:06

Кажется, вам нужна структура данных FIFO (первым пришел — первым обслужен), то есть очередь.

Вероятно, вы можете заменить весь свой код одним сценарием Perl.

Немного более легкая для чтения версия кода из моего комментария, использующая промежуточные переменные и измененная, чтобы разрешить возобновление работы из новой очереди:

прогулка_очередь:

#!/usr/bin/perl

use v5.10;
use strict;
use warnings;
use Tie::File;

my ($url_queue_file, $seen_urls_file) = @ARGV;

# allow reading/writing file lines as if they are arrays  
tie my @url_queue, 'Tie::File', $url_queue_file or die;
tie my @seen_urls, 'Tie::File', $seen_urls_file or die;

# (re)load lookup table from any previous results found
my %seen = map {$_=>1} @seen_urls;

# take url from head of queue
while (my $url = shift @url_queue) {
 
    # call wget - quote any metacharacters in url
    my $html = qx{ wget -qO- \Q$url\E };

    # extract href values that are https urls
    my @found_urls = ( $html =~ m{href = "(https://[^"#]+)"}g );

    # filter out irrelevant and duplicates
    my @new_urls = grep { m{magazine/} && !$seen{$_}++ } @found_urls;

    # add any new urls to tail of queue
    push @url_queue, @new_urls;

    # ensure starting url is always marked as seen 
    unshift @new_urls, $url if !$seen{$url}++;

    # append any new urls to result file
    push @seen_urls, @new_urls;

    # optionally output progress to screen
    say for @new_urls;
}

затем:

$ echo 'https://abc.xyz/start' >queue
$ cat /dev/null >found
$ ./walk_queue queue found
  • queue должен оказаться пустым
  • found должен содержать результаты (также отображаются на стандартном выводе)

Чтобы продолжить с другого URL-адреса, добавьте его в queue, но не удаляйте found, и запустите снова:

$ echo 'https://abc.xyz/another/one' >>queue
$ ./walk_queue queue found

(Я не тестировал этот код, так как не знаю ни фактического формата URL, ни содержимого HTML.)

Большое спасибо! Мне придется некоторое время переваривать код, perl — это буквально слово, которое я искал и установил несколько дней назад с помощью команды, скопированной/вставленной с этого форума, когда я искал ответы:)...интересно одно Команда -line, которую вы предложили ранее, запустите ее дюжину раз, каждый раз подавая новую URL-ссылку, она каждый раз выходит случайным образом из списка результатов, каждый раз, когда она будет следовать по URL-ссылкам «предыдущий/следующий» в обе стороны от начать, для первых нескольких сотен ссылок, затем после этого он «решит» идти только в одном направлении до конца, интересно, как он «решит»?

cimba8 12.05.2024 03:55

вероятно, потому, что ссылки с большей вероятностью будут дублироваться, поскольку они выполняются дольше - код комментария не возобновляется; он всегда начинается с пустого «видимого» хеша. вывод должен быть детерминированным для любого конкретного начального URL-адреса

jhnc 12.05.2024 05:06

Код работает без каких-либо изменений!... первый запуск завершается через 17 минут 28 секунд в моей студии m1 с 330 ссылками в found, а сейчас второй запуск

cimba8 12.05.2024 09:31

Обратите внимание, что очистка веб-сайта может быть недовольна его владельцем. и хотя, возможно, здесь это не проблема, в целом ограничение скорости считается вежливым, даже если парсинг допускается.

jhnc 12.05.2024 19:16

согласен... не более чем интерес, контент и код

cimba8 13.05.2024 02:08
Ответ принят как подходящий

Вот еще один подход с использованием bash coproc, которому, возможно, немного проще следовать, чем Perl.

прогулка_очередь2:

#!/bin/bash

start_url=$1
seen_urls_file=$2

# (re)load lookup table from previous results found
declare -A seen
if [[ -f $seen_urls_file ]]; then
    while IFS= read -r url; do
        seen["$url"]=1
    done < "$seen_urls_file"
fi
 
# feed in url, get back zero or more urls from its content
# then append "---" to let us track queue length
coproc extract_urls {
    while IFS= read -r url; do
        wget -qO- "$url" |
        grep -oE 'href = "https://[^"#]+"' |
        cut -d'"' -f2 |
        grep 'magazine/'

        echo ---
    done
}

queue_length=0
url=$start_url

while
    if (( !seen["$url"]++ )); then
        (( ++queue_length ))
        echo "$url" >& ${extract_urls[1]}
        echo "$url" >> "$seen_urls_file"
        echo "$url"
    fi
    IFS= read -r url
do
    while [[ $url = --- ]]; do
        (( --queue_length == 0 )) && break 2
        IFS= read -r url || break 2
    done
done <& ${extract_urls[0]}

Использовать как:

$ cat /dev/null >found
$ ./walk_queue2 'https://abc.xyz/start' found
$ ./walk_queue2 'https://abc.xyz/another/one' found

Приложение: Как обычно, coproc не нужен:

walk_queue3 (то же использование, что и выше):

#!/bin/bash

start_url=$1
seen_urls_file=$2

declare -a queue
qpos=0
queue[qpos]=$start_url

declare -A seen
if [[ -f $seen_urls_file ]]; then
    while IFS= read -r url; do
        seen["$url"]=1
    done < "$seen_urls_file"
fi

while (( ${#queue[@]} > qpos )); do

    # for simplicity, we don't delete from queue
    url=${queue[qpos++]}

    while IFS= read -r new_url; do
        if (( !seen["$new_url"]++ )); then
            queue+=("$new_url")
            echo "$new_url" >> "$seen_urls_file"
            echo "$new_url"
        fi
    done < <(
        wget -qO- "$url" |
        grep -oE 'href = "https://[^"#]+"' |
        cut -d'"' -f2 |
        grep 'magazine/'
    )
done

Обратите внимание, что замедление, вероятно, будет связано с регулированием со стороны владельца веб-сайта, который может возражать против очистки его веб-сайта.

Дополнительная еда тоже вкусная!

cimba8 12.05.2024 13:48

Другие вопросы по теме