Вопрос или проблема
Как я могу сделать так, чтобы встроенная команда read
поддерживала историю, позволяя использовать клавиши вверх/вниз для их перебора?
Я попытался поймать момент нажатия клавиши вверх, однако это, похоже, не работает с read
:
read -p '> ' -n 3 foo
echo
echo -n "$foo" | hexdump
Нажатие клавиш со стрелками, похоже, работает, и я могу это обнаружить, используя это, однако, если я нажму aa↑, это не сработает, так как будет считан только первый символ идентификатора стрелки вверх, в то время как для различения клавиш стрелок требуется третий.
Чтобы скопировать отличный ответ Майка Стройана из этого старого поста на почтовом списке:
Вы можете использовать “history -r” для чтения файла в историю оболочки
и “history -s” для добавления каждой строки, которую вы прочитали, в историю.
Затем используйте history -w, чтобы сохранить историю обратно в файл. Вот
пример с редактированием в стиле vi.
#!/bin/bash
history -r script_history
set -o vi
CMD=""
while true
do
echo "Введите что-нибудь"
read -e CMD
history -s "$CMD"
echo "Вы ввели $CMD"
case "$CMD" in
stop)
break
;;
history)
history
;;
esac
done
history -w script_history
echo остановлено
Вы можете использовать rlwrap
для этого, если не против установки программного обеспечения.
Вам, вероятно, следует сохранить отдельный файл истории, который будет содержать историю только для конкретного запроса в вашем скрипте (то есть избегать смешивания с историей команд оболочки пользователя).
Вот пример, который может вам подойти:
#!/bin/sh
# Сохраните в rlwrap_example.sh
HISTORY=$HOME/.myscript_history
USERINPUT=$(rlwrap -H $HISTORY sh -c 'read REPLY && echo $REPLY')
echo "Пользователь сказал $USERINPUT"
$ ./rlwrap_example.sh
hello
Пользователь сказал hello
В вышеприведенном скрипте пользователь может использовать все возможности GNU readline, с историей, предоставленной из — и хранящейся в — ~/.myscript_history
. Настройте по мере необходимости.
В качестве альтернативы, вы можете использовать read -e
в bash, который включает readline для вызовов read
, но вы, вероятно, обнаружите, что функциональность его истории слишком ограничена (то есть почти отсутствует).
mrb предлагает сделать это с помощью rlwrap
, что действительно очень полезно, но требует немного неаккуратного кода (в общем, в bash некоторые вещи сложны). Согласно мануалу rlwrap
, существует более читаемый способ выполнить чтение с историей (по крайней мере с версии 0.43 и выше).
Необходимость вызывать sh -c
связана только с тем, что read
является встроенной командой. И rlwrap, похоже, ожидает команду, которая существует как файл. Мануал предлагает использовать cat
вместо read
. Но cat считывает ввод после нажатия клавиши RETURN. Для этого опция -o отправляет EOF после нажатия RETURN. Вот немного измененный пример из мануала:
order=$(rlwrap -o -pYellow -S 'Ваше пицца? ' -H past_orders cat)
В оригинальном примере -o
стоит прямо перед cat
, и это может вызвать путаницу, как будто cat является его опцией.
В этом примере история сохраняется в текущем каталоге скрипта.
Ответ или решение
Вопрос о том, как сделать встроенную команду read
в bash поддерживающей историю ввода, является актуальным среди разработчиков и системных администраторов, стремящихся к улучшению пользовательского интерфейса своих скриптов. Давайте подробно разберем ваши вопросы и представим несколько решений.
Проблема с командой read
Команда read
в bash по умолчанию не поддерживает навигацию по истории с помощью клавиш стрелок. При нажатии клавиши вверх/вниз будет считан только первый символ кода управления, что делает этот подход неэффективным для работы с историей.
Возможные решения
Вот несколько вариантов, как можно улучшить функциональность read
в вашем bash-скрипте.
1. Использование history
в сочетании с read
Как предложил Mike Stroyan в своем ответе на рассылке, можно реализовать систему истории, используя команды history
и read
. Вам понадобятся следующие шаги:
#!/bin/bash
history -r script_history
set -o vi # или set -o emacs для режима редактирования
CMD=""
while true
do
echo "Введите команду:"
read -e CMD
history -s "$CMD" # сохраняем команду в историю
echo "Вы ввели: $CMD"
case "$CMD" in
stop)
break
;;
history)
history
;;
esac
done
history -w script_history # сохраняем историю в файл
echo "Завершение работы"
В данном скрипте мы загружаем историю из файла script_history
при запуске и сохраняем её обратно перед завершением работы скрипта.
2. Использование rlwrap
Если вас не смущает установка дополнительных утилит, вы можете использовать rlwrap
, который оборачивает команды и обеспечивает поддержку истории ввода через интерфейс readline. Ниже приведен пример:
#!/bin/sh
# Сохраните как rlwrap_example.sh
HISTORY=$HOME/.myscript_history
USERINPUT=$(rlwrap -H $HISTORY sh -c 'read REPLY && echo $REPLY')
echo "Пользователь ввел: $USERINPUT"
При запуске этого скрипта у пользователя будет возможность использовать функционал истории благодаря библиотеке GNU readline.
3. Оптимизация использования rlwrap
Согласно документации к rlwrap
, его можно упростить для лучшей читаемости. Рассмотрим следующий пример:
order=$(rlwrap -o -p "Желтый" -S 'Ваш заказ? ' -H past_orders cat)
Этот подход делает ваш код более читаемым и устраняет возможные недоразумения с параметрами команд.
Заключение
Каждый из предложенных методов имеет свои преимущества и недостатки. Если вы хотите простое решение, используйте встроенные команды и систему истории bash. Если вы стремитесь к более современному и функциональному решению, рассмотрите возможность интеграции rlwrap
. Настройка таких решений может значительно улучшить взаимодействие с пользователем и удобство ввода данных в ваших скриптах. Обязательно протестируйте выбранное вами решение в вашей среде, чтобы убедиться в его корректности и эффективности.