Как запустить многострочный awk-скрипт в bash

Я думаю, что ответ @ilkkachu полезен и дает то, что вам нужно. Постараюсь объяснить с некоторыми подробностями, а заодно научусь пользоваться fifo.

  • Подготовьте два окна командной строки, w1 и w2 на одном компьютере

  • Создать прослушивание program, я сделал шеллскрипт в w1

    #!/bin/bash
    
    while true
    do
     read string
     if [ "${string:0:4}" == "Stop" ]
     then
      printf "Gotcha\n"
      break
     elif [ "$string" != "" ]
     then
      printf "$string "
     else
      sleep 3
     fi
    done
    
  • Подготовить fifo в w1

    dir=$(mktemp -d)
    mkfifo "$dir/mypipe"
    
  • Запустите программу и дайте ей дождаться ввода из fifo в w1

    < "$dir/mypipe"./program
    
  • Ищите fifo и выводите в него несколько строк в w2

    $ find /tmp -name mypipe 2>/dev/null
    /tmp/tmp.dRhpqajJqz/mypipe
    
    $ > '/tmp/tmp.dRhpqajJqz/mypipe' echo qwerty
    $ > '/tmp/tmp.dRhpqajJqz/mypipe' echo asdf
    $ > '/tmp/tmp.dRhpqajJqz/mypipe' echo Stopp
    
  • Посмотрите вывод в w1

    qwerty asdf Gotcha
    $ 
    

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

  • снова запустить программу через w1

    < "$dir/mypipe"./program
    
  • в w2

    > $(find /tmp -name mypipe 2>/dev/null) echo 'Hello World'
    > $(find /tmp -name mypipe 2>/dev/null) echo 'Stop the World'
    
  • Посмотрите вывод в w1

    Hello World Gotcha
    $
    

Демонстрационная программа C,

#include <stdio.h>
#include <string.h>

int main () {

 char string[21];
 while(1){
    fgets(string, 20, stdin);
    string[strlen(string)-1] = 0;
    if(strcmp("Stop", string) == 0){
        printf("Gotcha");
        return 1;
    }
 }
}

Эта программа C записывает только после того, как прочитала 'Stop'.

0
12.03.2021, 04:50
4 ответа

Самый простой способ (с точки зрения удобочитаемости и ремонтопригодности ), на мой взгляд, состоит в том, чтобы отправить ваш awkскрипт во временный файл, чтобы затемawk:

получить его источник.
awksrc=$(mktemp) || exit 1
cat << 'EOF' > "${awksrc}"
{
  if ( $1 !~ /delete/ # ensure we are not trying to process deleted files
       && $4 !~ /theme.puml|config.puml/ # do not try to process our theme or custom config
       && $4 ~ /.puml/ 
  ) # only process puml files
      { printf "%s ", $4 } # only print the file name and strip newlines for spaces
}
END { print "" } # ensure we do print a newline at the end
EOF
echo "$GIT_OUTPUT" | awk -f "${awksrc}" 
rm -f "${awksrc}"
0
18.03.2021, 22:25

Основная проблема заключается в том, что код awkне заключен в кавычки, из-за чего оболочка заменяет в коде такие вещи, как $4. Чтобы защитить код от оболочки, убедитесь, что документ здесь -указан в кавычках. Вы получаете цитируемый здесь документ -, заключая начальное слово-разделитель в кавычки, как в <<'AWK'или <<"AWK", или экранируя его как <<\AWK.

Вот переделка вашего сценария так, как написал бы я:

git diff-tree -r --no-commit-id --summary "$GITHUB_SHA" |
awk '
    $1 !~ /^delete/ && $4 !~ /(theme|config)\.puml$/ && $4 ~ /\.puml$/ {
        name[++n] = $4
    }
    END {
        $0 = ""
        for (i in name) $i = name[i]
        printf "::set-output name=files::%s\n", $0
    }'

Обратите внимание, что я не храню промежуточные данные в переменных. Это неэффективно, (вы можете не знать , сколько данных вам нужно хранить в переменной, )и склонны делать ошибки в кавычках и вместо этого набрасывать значения на пробелы и вызывать подстановку имен файлов. Использование вами $GIT_OUTPUTи $AWKбез кавычек проблематично в этом отношении, а echo $GIT_OUTPUTособенно проблематично, поскольку echoможет изменить данные, если они содержат обратную косую черту, в зависимости от конфигурации оболочки.

О цитировании :См. Когда необходимо двойное -цитирование?

Я использую стандартный pattern { action }синтаксис сценария для создания массива nameстрок, которые вы хотите разобрать. В блоке ENDя создаю выходную запись $0, которую вывожу с префиксом, который вы использовали для вывода echo.

Вы также можете сделать это так, что оставляет немного больше места для комментариев:

git diff-tree -r --no-commit-id --summary "$GITHUB_SHA" |
awk '
    $1 ~ /^delete/ {
        # skip these
        next
    }
    $4 ~ /(theme|config)\.puml$/ {
        # and these...
        next
    }
    $4 ~ /\.puml$/ {
        # pick out filename (we assume no whitespace in filenames)
        name[++n] = $4
    }
    END {
        $0 = ""
        for (i in name) $i = name[i]
        printf "::set-output name=files::%s\n", $0
    }'

Если вы хотите настаивать на том, чтобы awkисходный код был в -документе здесь, я бы сделал это следующим образом:

awk_script=$(mktemp) || exit 1
trap 'rm -f "$awk_script"' EXIT

cat <<'AWK_CODE' >"$awk_script"
$1 !~ /^delete/ && $4 !~ /(theme|config)\.puml$/ && $4 ~ /\.puml$/ {
    name[++n] = $4
}
END {
    $0 = ""
    for (i in name) $i = name[i]
    printf "::set-output name=files::%s\n", $0
}
AWK_CODE

git diff-tree -r --no-commit-id --summary "$GITHUB_SHA" |
awk -f "$awk_script"

Т. е. сохранить сценарий awkво временный файл, который вызывается с помощью awk -fпозже и удаляется в конце сценария (здесь с помощьюtrap). Но для такой короткой awkпрограммыЯ не вижу в этом дополнительных преимуществ по сравнению с использованием скрипта в одной строке -в кавычках, как показано первым. Он беспорядочный и содержит множество дополнительных команд только для обслуживания, кроме двух центральных команд, которые необходимо выполнить.

2
18.03.2021, 22:25

Поместите свой код в функции, а не в переменные, что-то вроде этого (не проверено и еще есть место для улучшения):

set -x
set -e
do_awk() {
    awk '
        ($1 !~ /delete/) &&                 # ensure we are not trying to process deleted files
        ($4 !~ /theme.puml|config.puml/) && # do not try to process our theme or custom config
        ($4 ~ /.puml/) {                    # only process puml files
            printf "%s ", $4                # only print the file name and strip newlines for spaces
        }
        END { print "" }                    # ensure we do print a newline at the end
    ' "${@:--}"
}
GIT_OUTPUT=$(git diff-tree -r --no-commit-id --summary "$GITHUB_SHA")
AWK_OUPUT=$(printf '%s\n' "$GIT_OUTPUT" | do_awk)
echo "::set-output name=files::$GIT_OUTPUT"
set +e
set +x
2
18.03.2021, 22:25

Я никогда не использовал GitHub Workflow, но в документации сказано, что вы можете использовать пользовательскую оболочку. Казалось бы, если сказать:

steps:
  - name: process puml files
    run: <your awk script here>
    shell: awk -f {0}

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

0
18.03.2021, 22:25

Теги

Похожие вопросы