Запись в один и тот же файл с помощью awk и truncate

#linux #shell #awk #io #pipe

#linux #оболочка #awk #io #канал

Вопрос:

Моя система — Arch Linux, а мой оконный менеджер — DWM. Я использую dash в качестве интерпретатора оболочки.

Я написал этот сценарий оболочки расширения для своего таймера.

 
xev -root |
    awk -F'[ )] ' '/^KeyPress/ { a[NR 2] }
            NR in a {
                if ($8 == "Return") {
                    exit 0;
                } else if ($8 == "BackSpace") {
                    system("truncate -s-1 timer.txt");
                } else if (length($8) == 1) {
                    printf "%s", $8;
                    fflush(stdout);
                }            
                system("pkill -RTMIN 3 dwmblocks");
            }' | tee timer.txt
  

Сам таймер находится в строке состояния dwmblocks. Я хочу сначала назвать свои таймеры, а затем запустить его. Но я не думаю, что это так важно.

Цель этого скрипта — я хочу ввести символы в корневое окно DWM и мгновенно отобразить их в строке состояния. Итак, xev выдает информацию о нажатых клавишах, затем awk принимает эту информацию, находит точный ключ (из всей информации, которую выводит xev) и проверяет. Если ключ «Return», awk завершает работу (задание выполнено). Если ключом является «BackSpace», awk вызывает truncate из системы. Если это обычный символьный ключ, то awk выводит его в timer.txt с тройником (я мог бы использовать «> timer.txt «я думаю, тоже, но я хочу видеть выходные данные в моем терминале для отладки.

После каждого соответствующего нажатия клавиши (один символ) Я сбрасываю стандартный вывод. После всего этого я, наконец, вызываю pkill, чтобы dwmblocks знал, что он должен обновиться. (dwmblocks выдает операцию cat для файла)

Хорошо, «Возврат» и ввод символов работают нормально. Но есть проблема с «BackSpace». Я немного читал об этом (я бы сказал, что я все еще новичок в Unix, хотя я использую Linux уже два года), и я узнал, что запись в один и тот же файл из разных процессов — плохая новость. Тем не менее. Можно ли это как-то сделать? Дело в том, что truncate записывает в файл только тогда, когда awk этого не делает, так что, может быть, это было бы не так уж и важно?

Этот точный сценарий работал ранее вчера, но сейчас он не работает. Сначала я попытался использовать sed вместо truncate, и truncate, казалось, позволял мне удалять символы из timer.txt но теперь усечение, похоже, тоже больше не работает. Ну, это вроде как работает. Я могу вводить свои символы, а затем удалять их. НО.После нажатия Backspace я больше не могу вводить символы. Если я попытаюсь ввести символ, пробел тоже перестанет работать.

Так что да. У меня было бы несколько вопросов. Во-первых, в чем, черт возьми, проблема? Как я уже сказал, раньше это работало, а теперь нет. Я блуждаю в неопределенном поведении в этом скрипте?

Второе — можно ли это сделать — значение — могу ли я как-то записывать и удалять из того же файла. Может быть, с помощью какого-то другого инструмента, а не awk?

Заранее спасибо.

Ответ №1:

Вероятно, это не ответ, но это слишком много, чтобы указывать в комментарии. Я не знаю подробностей большинства инструментов, которые вы упоминаете, и я действительно не понимаю, что вы пытаетесь сделать, но:

Оболочка — это инструмент для управления файлами и процессами и планирования вызовов других инструментов. Awk — это инструмент для работы с текстом. Вы пытаетесь использовать awk как оболочку — у вас есть последовательность вызовов truncate и pkill и вызов system для создания подоболочки каждый раз, когда вы хотите выполнить любой из них. Что вы должны делать, например, просто:

 shell { truncate }
  

но то, что вы на самом деле делаете, это:

 shell { awk { system { shell { truncate } } } }
  

Можете ли вы забрать эту роль у awk и вернуть ее своей оболочке? Это должно сделать ваш общий сценарий проще, по крайней мере, концептуально и, вероятно, более надежным.

Может быть, попробуйте что-то вроде этого (непроверенный):

 #!/usr/bin/env bash

while IFS= read -r str; do
    case $str in
        Return ) exit 0 ;;
        BackSpace ) truncate -s-1 timer.txt ;;
        ? ) printf "%s" "$str" | tee -a timer.txt ;;
    esac
    pkill -RTMIN 3 dwmblocks
done < <(
    xev -root |
    awk -F'[ )] ' '/^KeyPress/{a[NR 2]} NR in a{print $8; fflush()}'
)
  

Я переместил запись в timer.txt внутри цикла, чтобы убедиться tee , что вы не пытаетесь записать в него, пока вы его усекаете — в этом может и не быть необходимости.

Комментарии:

1. Ну, это не ответ на первый вопрос, но это ответ на второй! Да, это отлично работает, и спасибо, что указали на глубокое разделение truncate в shell в system в awk в shell. ДА. Я не думал об этом много, я думаю. Ваше решение отлично работает, похоже, и оно очень элегантное. Единственное, что мне осталось сделать, это перенести его на dash, поскольку я использую только dash в качестве своего интерпретатора по «забавным» причинам. Большое спасибо за помощь!

2. О, и последнее, что касается сценария. Это не работает так, как есть. Мне нужно было добавить truncate -s0 timer.txt перед циклом while и удалить -a из tee -a timer.txt команды, но я думаю, это только потому, что программное обеспечение в строке состояния, которое я использую, каким-то образом испортилось. Я имею в виду, что это работает, но dwmblocks неправильно отображает содержимое файла

3. Окончательное решение получилось так: #!/bin/sh truncate -s0 timer.txt xev -root | awk -F'[ )] ' '/^KeyPress/{a[NR 2]} NR in a{print $8; fflush()}' | while IFS= read -r str; do case $str in Return ) exit 0 ;; BackSpace ) truncate -s-1 timer.txt ;; ? ) printf "%s" "$str" | tee -a timer.txt ;; esac pkill -RTMIN 3 dwmblocks done