Запускать каждую команду bash через функцию или оболочку

попробуй

find... | awk '{$1 = "rm" ; print } ' | bash

это заменит фактическую сумму md5(aaf...)на rm.

это не будет работать, если в имени файла есть специальный символ, а также если файл защищен от записи (замените rmнаrm -f).

4
14.01.2020, 13:42
1 ответ

Bash не настолько гибок, вы можете запускать команды/функции до и после каждой командной строки -, но сбор вывода только в bash не -тривиален.

Некоторые проблемы включают:

  • запись в канал отличается от записи в терминал или файл (например. ioctl(), fseek(), fstat()), многие программы определяют это (как manи дажеls). Вы не захотите использовать это с интерактивным редактором, таким как vi.
  • специальные команды, такие как " time", конвейеры и даже циклы оболочки могут усложнять решения, а конвейеры изменяют коды возврата
  • вы не можете легко «захватить» каждую возможную команду с помощью псевдонима, и такой псевдоним нарушит параметры/аргументы командной строки, потому что aliasтолько лексически заменяет команду
  • вы не можете просто (поверх )записать один файл, т.е. есть ли гонка, когда вы выдаете команду для проверки последнего вывода?
  • если вы хотите захватить stdoutи stderr, у вас могут возникнуть проблемы с чередованием
  • если вы используете канал, помните, что bash создает подоболочки, когда есть каналы и некоторые другие перенаправления, один непосредственный побочный эффект заключается в назначении переменной :вместо этого они назначаются в подоболочке (, а затем отбрасываются, оставляя он не изменился в вашей текущей оболочке ). "."(sourceexitзатронуты аналогичным образом. (В приведенном ниже варианте использования readline Ctrl-j можно использовать для запуска таких команд.)

Это (примерно )то, что я использую для аналогичной проблемы, функции, называемые +, ++и т. д., поэтому я могу префиксировать только те команды, для которых я хочу захватить/обработать вывод, например к выбору X или буферу обмена:

function +() (
  set -f  # no double expand
  eval "$@" | tee >( tr -s "\n" " " | xclip )
  return ${PIPESTATUS[0]}  # real rc
)  

напр. + find /tmp -name "*.pdf" -mtime -30Это несовершенно, но в основном делает то, что мне нужно.

Выше @mosvy рекомендует script,это создает новый терминал tty (), в котором запускается программа, которая обходит все эти проблемы. Он записывает весь вывод на родительский tty, но также записывает его в файл. Он записывает либо весь сеанс, либо одну команду, поэтому у вас все еще есть проблема с тем, как его автоматически вызвать.

Вместо этого используйте screen(, потому что в отличие от scriptможно взаимодействовать и управлять работающим экземпляром )и bash PROMPT_COMMAND:

.
if [[ "$TERM" -eq "screen" && -n "$STY" ]]; then
    PROMPT_COMMAND=screenlog
    SCREENLOG="${HOME}/screenlog"
fi

function screenlog() {
    [[ -n "$STY" ]] && {
        screen -S "$STY" -X log off
        [[ -f "${SCREENLOG:=$HOME/screenlog}" ]] && mv "$SCREENLOG" /tmp/last_command
        screen -S "$STY" -X logfile "$SCREENLOG"
        screen -S "$STY" -X log on
    }
    return 0
}

Если указанное выше находится в вашем .bashrc, вы сможете просто запустить screen. Вышеупомянутая функция вызывается каждый раз, когда bash отображает новую командную строку. Сначала он проверяет, что он работает под экраном, затем обращается к экрану, чтобы выдать команды для остановки ведения журнала, записывает последний вывод в /tmp/last_command, а затем приказывает экрану начать ведение журнала.

Одно предостережение заключается в том, что, поскольку PROMPT_COMMANDвызывается до отображения подсказки, и подсказка, и команда (, включая такие изменения, как возврат ), появятся в начале файла журнала. Один из способов обойти это — эмулировать «pre -exec» с ловушкой DEBUG, чтобы вместо этого запустить вышеуказанное, например:Измените все команды bash через программу перед их выполнением . Если вы запускаете «интерактивные» или терминальные программы (, например. manилиtop)вы также получите записанные последовательности управления терминалом, что полезно, если вы хотите воспроизвести вывод (, хотя script/ scriptreplayв этом отношении лучше ).


Внутри bash используется readline, что позволяет привязывать ключи к функциям readline, последовательностям других ключей или функциям оболочки (, но не комбинациям трех типов ). Это относительно легко сделать:

bind '"\C-e": end-of-line'
bind '"\C-j": accept-line'
bind '"\C-m": "\C-e | tee /tmp/last_command\C-j"'

Первые две строки просто паранойя, чтобы убедиться, что Ctrl-E и Ctrl-J привязаны как положено (, если у вас есть терминал без accept-lineсвязан, у вас больше нет терминала... ).Последняя строка заменяет Ctrl-M (, также известную как «Return» ), которая обычно связана с accept-line, последовательностями:

  1. Ctrl-E(конец -строки -)
  2. | tee /tmp/last_command(буквальный текст)
  3. Ctrl-J(принять -строку)

Это видно на экране и в истории по мере изменения самой выданной команды. Недостатком этого простого подхода является то, что он не является «самоосознающим» -и слепо изменяет каждую команду, даже ту, что была в истории, с уже примененным изменением.

Вот более сложная версия, в которой используется функция для изменения команд по мере их отправки:

bind '"\C-e": end-of-line'
bind '"\C-j": accept-line'
bind -x '"\e\C-a": _recorder'

function _record() {
    tee /tmp/this_command
    [[ -f /tmp/this_command ]] && mv /tmp/this_command /tmp/last_command
}

function _recorder() {
    local text="| _record"
    [[ -z "${READLINE_LINE}" ]] && return 0
    [[ "$READLINE_LINE" =~ "${text}"$ ]] || READLINE_LINE+=" $text"
    return 0
}
bind '"\C-m": "\C-e\e\C-a\C-j"'

Шаг 2 заменен bash-функцией _recorder, привязанной к Esc , Ctrl-a (, которую не нужно набирать, ее просто нужно привязан к нажатию клавиши, потому что макрос может содержать только нажатия клавиш ).

Эта функция может быть сколь угодно умной, здесь она обрабатывает гонку выходных файлов и проверяет, что канал еще не присутствует, перед изменением входной строки. Вы также можете обернуть всю командную строку в подоболочку, если присутствуют другие перенаправления (, хотя вы быстро обнаружите, что синтаксический анализ командных строк bash в bash будет, хм, сложным ).

Можно и дальше (! )усугубить взлом, исправив историю на лету:

function myprompt() {
    local _seq _cmdline
    local text="| _record"
    read _seq _cmdline < <(HISTTIMEFORMAT= history 1)  # previous command
    [[ "${_cmdline}" =~ (.*)" ${text}"$ ]] && {
        _cmdline="${BASH_REMATCH[1]}"
        history -d $_seq        # delete entry
        history -s "$_cmdline"  # restore original
    }
}
PROMPT_COMMAND=myprompt

Подход readline имеет еще один -нетривиальный недостаток :не-простые команды, которые обычно можно вводить в несколько строк, при этом подходе нарушается, например. (while, for).

3
27.01.2020, 21:01

Теги

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