Как вызвать bash, запустить команды внутри новой оболочки, а затем вернуть управление пользователю?

86

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

Я старался:

$ bash -lic "some_command"

но это выполняется some_commandвнутри нового экземпляра, а затем закрывает его. Я хочу, чтобы он оставался открытым.

Еще одна деталь, которая может повлиять на ответы: если я смогу заставить это работать, я буду использовать его в своих .bashrcпсевдонимах, так что бонусные баллы за aliasреализацию!

Феликс Сапарелли
источник
Я не понимаю последнюю часть о псевдонимах. Псевдонимы выполняются в контексте текущей оболочки, поэтому у них нет проблемы, которую вы просите решить (хотя обычно функции лучше, чем псевдонимы, по ряду причин).
Tripleee
2
Я хочу иметь возможность поместить что-то вроде alias shortcut='bash -lic "some_command"'моего .bashrc, затем запустить shortcutи получить новую оболочку, которая some_commandуже запущена в ней.
Феликс Сапарелли

Ответы:

63
bash --rcfile <(echo '. ~/.bashrc; some_command')

избавляет от создания временных файлов. Вопрос на других сайтах:

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
Я попытался сделать это в Windows 10 (попытался написать пакетный «запускающий» файл / скрипт) и получилThe system cannot find the file specified.
Doctor Blue
1
@Scott отлаживает пошагово: 1) Работает cat ~/.bashrc? 2) Работает cat <(echo a)? 3) Работает bash --rcfile somefile?
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
1
@Scott, мы только что столкнулись с этой проблемой на WSL (запуск bash из командного файла запуска). Нашим решением было экранировать некоторые символы, чтобы пакет мог их понять: bash.exe --rcfile ^<^(echo 'source $HOME/.bashrc; ls; pwd'^)должен запускаться из командной строки Windows.
user2561747
Есть ли способ заставить его работать с переключением пользователей, например sudo bash --init-file <(echo "ls; pwd")или sudo -iu username bash --init-file <(echo "ls; pwd")?
jeremysprofile
39

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

Насколько я могу судить, у bashнего нет возможности делать то, что хотел сделать исходный плакат. -cВариант всегда будет возвращаться после того , как команды были выполнены.

Сломанное решение : простейшая и очевидная попытка обойти это:

bash -c 'XXXX ; bash'

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

Лучше : способ обойти это - динамически создать файл запуска и вызвать bash с этим новым файлом инициализации, убедившись, что ваш новый файл инициализации ~/.bashrcпри необходимости вызывает ваш обычный .

# Create a temporary file
TMPFILE=$(mktemp)

# Add stuff to the temporary file
echo "source ~/.bashrc" > $TMPFILE
echo "<other commands>" >> $TMPFILE
echo "rm -f $TMPFILE" >> $TMPFILE

# Start the new bash shell 
bash --rcfile $TMPFILE

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

Примечание. Я не уверен, что / etc / bashrc обычно вызывается как часть обычной оболочки без входа в систему. Если это так, вы можете захотеть использовать источник / etc / bashrc, а также ваш ~/.bashrc.

Давераджа
источник
rm -f $TMPFILEВещь делает это. На самом деле я не помню вариант использования, который у меня был для этого, и теперь я использую более продвинутые методы автоматизации, но это правильный путь!
Феликс Сапарелли
7
Нет файлов tmp с заменой процессаbash --rcfile <(echo ". ~/.bashrc; a=b")
Ciro Santilli 郝海东 冠状 病 六四 事件
3
Временный файл будет очищен только в том случае, если ваш Bash-скрипт запустится успешно. Более надежным решением было бы использование trap.
tripleee
5

Вы можете передать --rcfileBash, чтобы он прочитал файл по вашему выбору. Этот файл будет прочитан вместо вашего .bashrc. (Если это проблема, ~/.bashrcиспользуйте другой скрипт.)

Изменить: Таким образом, функция для запуска новой оболочки с материалом ~/.more.shбудет выглядеть примерно так:

more() { bash --rcfile ~/.more.sh ; }

... и у .more.shвас будут команды, которые вы хотите выполнить при запуске оболочки. (Я полагаю, было бы элегантно избежать отдельного файла запуска - вы не можете использовать стандартный ввод, потому что тогда оболочка не будет интерактивной, но вы можете создать файл запуска из документа здесь во временном месте, а затем прочитать его.)

тройной
источник
3

Вы можете получить желаемую функциональность, взяв сценарий вместо его запуска. например:

$ cat скрипт
cmd1
cmd2
$. сценарий
$ в этот момент cmd1 и cmd2 были запущены внутри этой оболочки
Уильям Перселл
источник
Хм. Это работает, но разве нет возможности встроить все в alias=''файл?
Феликс Сапарелли
1
Никогда не используйте псевдонимы; вместо этого используйте функции. Вы можете указать: 'foo () {cmd1; cmd2; } 'внутри ваших сценариев запуска, а затем выполнение foo запустит две команды в текущей оболочке. Обратите внимание, что ни одно из этих решений не дает вам новую оболочку, но вы можете выполнить «exec bash», а затем «foo»
Уильям Перселл
1
Никогда не говори никогда. псевдонимы имеют свое место
Гленн Джекман
Есть ли пример варианта использования, в котором функцию нельзя использовать вместо псевдонима?
Уильям Перселл
3

Добавьте в ~/.bashrcтакой раздел:

if [ "$subshell" = 'true' ]
then
    # commands to execute only on a subshell
    date
fi
alias sub='subshell=true bash'

Затем вы можете запустить подоболочку с помощью sub.

Dashohoxha
источник
Очки за оригинальность. Сам псевдоним, вероятно, лучше как env subshell=true bash(поскольку он не просочится $subshellв последующие команды): Использование env против использования экспорта .
Феликс Сапарелли,
Ты прав. Но я заметил, что он работает и без него env. Для проверки того, определен он или нет, я использую echo $subshell(вместо того, env | grep subshellчто используете вы).
dashohoxha
3

Принятый ответ действительно полезен! Просто добавлю, что подстановка процесса (т.е. <(COMMAND)) не поддерживается в некоторых оболочках (например, dash).

В моем случае я пытался создать настраиваемое действие (в основном однострочный сценарий оболочки) в файловом менеджере Thunar для запуска оболочки и активации выбранной виртуальной среды Python. Моя первая попытка была:

urxvt -e bash --rcfile <(echo ". $HOME/.bashrc; . %f/bin/activate;")

где %f- путь к виртуальной среде, обрабатываемой Thunar. Я получил ошибку (запустив Thunar из командной строки):

/bin/sh: 1: Syntax error: "(" unexpected

Потом я понял, что мой sh(по сути dash) не поддерживает подмену процессов.

Мое решение заключалось в том, чтобы вызвать bashна верхнем уровне интерпретацию подстановки процесса за счет дополнительного уровня оболочки:

bash -c 'urxvt -e bash --rcfile <(echo "source $HOME/.bashrc; source %f/bin/activate;")'

В качестве альтернативы я попытался использовать здесь-документ, dashно безуспешно. Что-то типа:

echo -e " <<EOF\n. $HOME/.bashrc; . %f/bin/activate;\nEOF\n" | xargs -0 urxvt -e bash --rcfile

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

Хаймо
источник
Если кто-то не хочет использовать bash поверх (или не имеет его), существуют различные документированные способы прямой реализации подстановки процессов, в основном с помощью fifos.
Феликс Сапарелли
2

В соответствии с ответом Давераджи , вот сценарий bash, который решит эту задачу.

Рассмотрим ситуацию, если вы используете C-shell и хотите выполнить команду, не выходя из контекста / окна C-shell следующим образом:

Выполняемая команда : искать точное слово Testing в текущем каталоге рекурсивно только в файлах * .h, * .c

grep -nrs --color -w --include="*.{h,c}" Testing ./

Решение 1. Войдите в bash из C-оболочки и выполните команду

bash
grep -nrs --color -w --include="*.{h,c}" Testing ./
exit

Решение 2. Запишите предполагаемую команду в текстовый файл и выполните ее с помощью bash.

echo 'grep -nrs --color -w --include="*.{h,c}" Testing ./' > tmp_file.txt
bash tmp_file.txt

Решение 3. Запустите команду в той же строке, используя bash

bash -c 'grep -nrs --color -w --include="*.{h,c}" Testing ./'

Решение 4. Создайте sciprt (одноразовый) и используйте его для всех будущих команд

alias ebash './execute_command_on_bash.sh'
ebash grep -nrs --color -w --include="*.{h,c}" Testing ./

Сценарий выглядит следующим образом:

#!/bin/bash
# =========================================================================
# References:
# https://stackoverflow.com/a/13343457/5409274
# https://stackoverflow.com/a/26733366/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://www.linuxquestions.org/questions/other-%2Anix-55/how-can-i-run-a-command-on-another-shell-without-changing-the-current-shell-794580/
# https://www.tldp.org/LDP/abs/html/internalvariables.html
# https://stackoverflow.com/a/4277753/5409274
# =========================================================================

# Enable following line to see the script commands
# getting printing along with their execution. This will help for debugging.
#set -o verbose

E_BADARGS=85

if [ ! -n "$1" ]
then
  echo "Usage: `basename $0` grep -nrs --color -w --include=\"*.{h,c}\" Testing ."
  echo "Usage: `basename $0` find . -name \"*.txt\""
  exit $E_BADARGS
fi  

# Create a temporary file
TMPFILE=$(mktemp)

# Add stuff to the temporary file
#echo "echo Hello World...." >> $TMPFILE

#initialize the variable that will contain the whole argument string
argList=""
#iterate on each argument
for arg in "$@"
do
  #if an argument contains a white space, enclose it in double quotes and append to the list
  #otherwise simply append the argument to the list
  if echo $arg | grep -q " "; then
   argList="$argList \"$arg\""
  else
   argList="$argList $arg"
  fi
done

#remove a possible trailing space at the beginning of the list
argList=$(echo $argList | sed 's/^ *//')

# Echoing the command to be executed to tmp file
echo "$argList" >> $TMPFILE

# Note: This should be your last command
# Important last command which deletes the tmp file
last_command="rm -f $TMPFILE"
echo "$last_command" >> $TMPFILE

#echo "---------------------------------------------"
#echo "TMPFILE is $TMPFILE as follows"
#cat $TMPFILE
#echo "---------------------------------------------"

check_for_last_line=$(tail -n 1 $TMPFILE | grep -o "$last_command")
#echo $check_for_last_line

#if tail -n 1 $TMPFILE | grep -o "$last_command"
if [ "$check_for_last_line" == "$last_command" ]
then
  #echo "Okay..."
  bash $TMPFILE
  exit 0
else
  echo "Something is wrong"
  echo "Last command in your tmp file should be removing itself"
  echo "Aborting the process"
  exit 1
fi
Рохан Гиге
источник
2
Это совершенно другой ответ на совершенно другую проблему. (Здорово, что вы это догадались, но это не относится к этой теме. Если этого не существует на этом сайте, подумайте о том, чтобы открыть новый вопрос со вторым предложением, а затем немедленно ответить на него остальными ... вот как это должно быть обработано ☺) Также см. ответ Чиро Сантилли, чтобы узнать способ сделать это вообще без создания временного файла.
Феликс Сапарелли 09
Помечено как "не-ответ", поскольку это не попытка ответить на этот вопрос (хотя это действительно отличная попытка ответить на другой вопрос!)
Чарльз Даффи,
0

Вот еще один (рабочий) вариант:

Это открывает новый терминал gnome, а затем в новом терминале запускает bash. Сначала читается rc-файл пользователя, затем команда ls -laотправляется на выполнение в новую оболочку, прежде чем она станет интерактивной. Последнее эхо добавляет дополнительную строку новой строки, которая необходима для завершения выполнения.

gnome-terminal -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'

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

gnome-terminal --profile green -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'
user1656671
источник
-1

Выполнение команд в фоновой оболочке

Просто добавьте &в конец команды, например:

bash -c some_command && another_command &
рафтлв
источник
-1
$ bash --init-file <(echo 'some_command')
$ bash --rcfile <(echo 'some_command')

Если вы не можете или не хотите использовать подстановку процесса:

$ cat script
some_command
$ bash --init-file script

По-другому:

$ bash -c 'some_command; exec bash'
$ sh -c 'some_command; exec sh'

sh-only way ( dash, busybox):

$ ENV=script sh
x-yuri
источник
Хорошие альтернативы, но было бы лучше, если бы верхняя часть (перед ENV=scriptчастью) была удалена или перефразирована, чтобы избежать путаницы с явной копипастой верхнего ответа.
Феликс Сапарелли
@ FélixSaparelli Честно говоря, все решения, кроме ENV+ sh, присутствуют в других ответах, но в основном похоронены в тоннах текста или окружены ненужным / несущественным кодом. Я считаю, что краткое и ясное резюме принесет пользу всем.
x-yuri