231

I have three scripts I need to run when I start up my Ubuntu machine, they start services I use in my development environment.

To do that, I manually open three terminals and type in the commands.

Is there any way to create a script that will open three terminals and execute one command in each of these? (Each command should be in a separate terminal window so I can see their output).

Flimm
  • 41,766
JSBach
  • 2,481

8 Answers8

225
gnome-terminal -- command

or

xterm -e command

or

konsole -e command

Pretty much

terminal -e command

To make the terminal stay when the command exits:

In konsole there is a --noclose flag.

In xterm, there is a -hold flag.

In gnome-terminal, go to Edit -> Profile Preferences -> Title. Click the Command tab. Select Hold the terminal from the drop-down menu labelled When command exits. You should create a new profile for that and execute with

gnome-terminal --window-with-profile=NAMEOFTHEPROFILE -e command
Pablo Bianchi
  • 15,657
  • 2
    If I try to hold the terminal, I get "child process exited normally with status code 127" – Darshan Chaudhary Aug 12 '16 at 07:49
  • 2
    gnome-terminal does not have the title option any more :( – törzsmókus Feb 22 '17 at 13:04
  • @törzsmókus It works in Ubuntu 14.04 with gnome-terminal 3.6.2. What distro and version of gnome-terminal are you using? – bhass1 Mar 02 '17 at 05:21
  • 16.04 LTS, gnome-terminal 3.18.3. @bhass1 it is 2017… – törzsmókus Mar 02 '17 at 11:58
  • 1
    @törzsmókus it is 2017 indeed! LTS releases have a 5 year support life. 14.04 does not end until April 2019. https://wiki.ubuntu.com/Releases – bhass1 Mar 05 '17 at 22:33
  • 1
    gnome-terminal -e command only works if command is quoted. So this does not work: gnome-terminal -e "echo hello world; sleep 3" but this does: gnome-terminal -e "bash -c 'echo hello world; sleep 3'". Sigh. – bgoodr Apr 16 '17 at 19:47
  • 5
    consider using now: gnome-terminal -- command – dallonsi Jul 01 '19 at 07:35
  • how to find default app variables or wrappers so that command would be running in any distro terminal apps? How to find also default browser or text editor variable? – Kangarooo Mar 07 '20 at 15:31
  • I get this warning: Option “-e” is deprecated and might be removed in a later version of gnome-terminal. better to use -- instead of -e like the example in @dallonsi comment – Tim Apr 10 '20 at 20:40
  • Using gnome-terminal I get: "The child process exited normally with status 0." It holds the terminal open but not in a way that I can actually use it after. It is just stuck. – Kvothe Nov 19 '20 at 14:53
  • I'd like to confirm that any flags such as --hold should be used before -e In other words -e should be followed by the command only. – SaidbakR Apr 30 '21 at 23:13
87

Instead of hard-coding gnome-terminal, konsole, et cetera, use the Alternatives system. The program that executes the default terminal emulator is:

x-terminal-emulator

On my system, it opens a new instance of Konsole every time I execute this command.

Luckily, the terminals seems to support the -e option for executing a command (I verified it for konsole and gnome-terminal). Arguments after the command are passed to the invoked command. Bash refuses to stay open in my terminal, an additional script is needed to get a terminal:

#!/bin/sh
"$@"
exec "$SHELL"

If you've saved the previous script as /home/user/hacky and made it executable, you would run your scripts with:

x-terminal-emulator -e /home/user/hacky your-script optional arguments here

The full path is required and /home/user/hacky has to be executable.

My previous attempt to run a script in a new terminal window can be found in revision #2, it was before I realised arguments can be passed to x-terminal-emulator.

Lekensteyn
  • 174,277
  • In this case, it won't help much as the asker wants to do something that isn't the same for all terminals. – nickguletskii Jun 02 '11 at 20:36
  • Attempt #3: this one should keep the terminal open and run the program with optional arguments. – Lekensteyn Jun 02 '11 at 21:06
  • 1
    I used gnome option, however, once I run my script, the main terminal closes !! .. any idea why ? – McLan May 20 '15 at 14:33
  • 3
    @Suda.nese That is by design, when the "terminal" is done executing the script it will quit because there is nothing more to do. You could "fix" this by invoking a shell where you can execute commands (bash) or have a line such as read -p "Press Return to continue". – Lekensteyn May 20 '15 at 15:44
  • 1
    How can you run more than one command in the terminal? For example cd xxx && start.sh. The interpretor sees the && as the second part of the command (which is logical), but if I quote it, then it tries to exec the whole thing as one big argument – Richard May 25 '17 at 07:02
  • @Richard same here, I want to run multiple commands, but it fails – Adam Hunyadi Jun 09 '17 at 07:29
  • On my system, x-terminal-emulator opens Terminator, but my default terminal is xfce4-terminal in "Preferred Applications". Why? – Aaron Franke Sep 26 '17 at 23:08
  • @AaronFranke I guess that x-terminal-emulator is configured by the update-alternatives system configuration while "Preferred Applications" is configured and stored in the user configuration. – Lekensteyn Sep 28 '17 at 10:11
  • BTW how to run many commands in a single run in the opened terminal emulator? – Rudresh Dixit Oct 05 '20 at 10:43
22

UPDATE 17 FEB 2020: this answer is now perhaps obsolete.

Consider clicking this link and using this other answer of mine instead: Open Terminal with multiple tabs and execute application


Aided by @nickguletskii's answer, and my own comment under his answer, and inspired by @grabantot's upvote of my comment, here's my preferred way to do it, especially when I want the terminal to stay open so I can then manually use it.

Ex. usage: this is really useful to add to your startup programs so this script will run, open a terminal, create and name a tab in the terminal, and run a command for you. Or, you can just add a symlink to this script to your desktop. I use this type of approach so I can double-click a single icon on my desktop and have it open up a bunch of terminals (with various tabs named according to what work I'm going to do in them) and programs to set up my programming environment, for instance, for daily work.

Here's a contrived example, which opens up a single tab, titles it "test", then runs the simple command cd /etc; ls inside it. The $SHELL part at the end forces the shell to stay open so you can then see its output and continue using it (I learned this somewhere else on Stack Overflow):

gnome-terminal --tab --title="test" --command="bash -c 'cd /etc; ls; $SHELL'"

Here's a more complicated example which opens up 3 separate tabs in the same gnome-terminal. This is exactly the type of thing my desktop shortcut does so I can open up a bunch of programming windows at once:

gnome-terminal --tab --title="tab 1" --command="bash -c 'cd /etc; ls; $SHELL'" --tab --title="tab 2" --command="bash -c 'cd ~; ls; $SHELL'" --tab --title="tab 3" --command="bash -c 'cd ~/temp3; ls; $SHELL'"

Here's a breakdown of that command above:

  • gnome-terminal = open up a gnome-terminal
  • --tab = open up a unique tab for what comes next
  • --title="tab 1" = title this tab "tab 1"
  • --command="bash -c 'cd /etc; ls; $SHELL'" = run the bash -c 'cd /etc; ls; $SHELL' command, which is a command I just made up as an example; here's what it does:
  • bash -c says it is a bash 'c'ommand
  • cd /etc = 'c'hange 'd'irectory into the "/etc" path
  • ls = 'l'i's't contents of this directory
  • $SHELL = this cryptic tidbit is required to keep the shell open so you can work with it. If you want the shell to open, run your command, then close, simply remove this part. I, however, want the tab to stay open so I can go make programming magic. :)
  • we then start back over at the --tab part to produce tab 2, then again for tab 3. Customize to your heart's content.

Screenshot:

enter image description here

  • 1
    glad I was helpful) I also have scripts that I can just click on and start working on the project. There were two problems with them: lots of terminal windows (had a whole separate screen for them) and windows closing after server crashes for example. That answer solves both of my problems with --tab + $SHELL. Nice – grabantot Dec 31 '18 at 09:33
  • How can I open a tab and set it as active, at the moment I open a tab and then have to click on it as well to make it the active tab – Alfa Bravo Aug 21 '20 at 17:47
  • @AlfaBravo, not sure. You might consider scripting actual keyboard presses as though a human had done them. Ex: xdotool key --clearmodifiers Super+d, as I use in my show-desktop.desktop file, presses Windows Key + D to toggle showing the desktop--same as if a human had done these key presses. I'm sure you could just script pressing Ctrl + PgUp or Ctrl + PgDn the right number of times to select the tab you want, as that's how you can switch tabs manually as a human. – Gabriel Staples Aug 21 '20 at 18:01
  • The gnome-terminal --tab --title="tab 1" example was exactly what I'm looking for. However, on Ubuntu 22.10 I get these warnings: `# Option “--command” is deprecated and might be removed in a later version of gnome-terminal.

    Use “-- ” to terminate the options and put the command line to execute after it.`

    – tim11g Mar 19 '23 at 19:51
  • I tried the suggestion, but it doesn't work for this case where multiple tabs are created. After the first "-- " it will execute the first tab's command, but then does not parse any subsequent "--tab" options. Is there a different syntax needed when multiple tabs and commands are used together? – tim11g Mar 19 '23 at 20:05
  • @tim11g, that's exactly why I added to the beginning of this answer: "UPDATE 17 FEB 2020: this answer is now perhaps obsolete." I then link to a better answer that works now.. Search my ~/.bash_aliases file here for the gs_open_default_tabs function as an example. Getting this all working has been quite tricky, but it works fine now on Ubuntu 16.04, 18.04, 20.04, and 22.04, per my testing. – Gabriel Staples Mar 20 '23 at 19:38
10

Quite simply-

#!/bin/bash

/etc/init.d/ccpd status

This is enough for other commands that do not need to display anything in terminal. But here one has to see the status displayed.
So, it needs to run in a terminal window

#!/bin/bash

gnome-terminal -e "/etc/init.d/ccpd status"  --window-with-profile=NAMEOFTHEPROFILE

The other post intended [] to be a placeholder

Here "NAMEOFTHEPROFILE" is to be replaced with the name of the profile that "Holds the terminal when the command exits".

enter image description here

enter image description here

Karthik T
  • 2,031
  • 1
    @cipricus i believe [] was just placeholder – Karthik T Jan 23 '13 at 09:37
  • got it. but i have to make the terminal not to close so fast. i guess that is also in the linked question –  Jan 23 '13 at 09:43
  • @cipricus have you tried the profile one? Just need to add --window-with-profile=NAMEOFTHEPROFILE to what I have given – Karthik T Jan 23 '13 at 09:44
  • that part with the profile is still unclear to me. maybe if you put it very clearly in your answer. but the terminal stands if i follow the advice from the linked answer: "In gnome-terminal, go to Edit->Profile Preferences->Title and Command tab->Select "Hold the terminal" from the drop down box labelled "When command exits. " –  Jan 23 '13 at 09:48
  • 1
    @cipricus I would have to get back home to give better instructions, but the idea is to create a special profile with that option set, and use the name of the special profile in the place above. – Karthik T Jan 23 '13 at 09:51
  • 2
    @cipricus if that is enough for you, then that is ok. Profile is nothing more than a group of settings. You can setup the settings ONLY for use in your script, and not have to use it in all terminals. You can see Edit -> Profiles to see all the profiles you have, and you would add one there which was setup as explained in the post you linked – Karthik T Jan 23 '13 at 15:39
3

Almost a decade late to the party but, here's my answer using Python.

In the .gif below I launched the program from an existing terminal with screen recorder running to show what it would look like at login:

dellstart.gif

I wrote a python program for this answer. There are some extra features not requested by OP but beneficial to me:

  • Runs on autostart to setup GUI applications frequently used after login.
  • Opens multiple gnome-terminal tabs.
  • Assign title to terminal tabs.
  • Moves windows to preferred position on desktop.
  • Opens gedit and last five opened files in separate tabs.

The python program:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

#==============================================================================

dellstart - Autostart GUI applications on Dell Fileserver

#==============================================================================

''' CALL:

dellstart

REQUIRES:

sudo apt install xdotool

'''

from future import print_function # Must be first import import os import time

BASHRC_TIME = 2 # Seconds to load ~/.bashrc WINDOW_TIME = .5 # Secpmds fpr window to appear

commands = [ 'gnome-terminal &', # Launch terminal in background 'sleep '+str(BASHRC_TIME), # Bash command wait a sec 'move x y', # Move windows to x and/or y

'move 2100 1000', # triple monitor setup

         'xdotool type "cd ~"',             # Change to home directory
         'xdotool key Return',              # Enter Key
         'xdotool type "./ssh-activity"',   # Suspend after 15 minutes
         'xdotool key Return',              # Enter Key
         'title SSH\ Activity',             # Window title (escape spaces)
         'xdotool key Control_L+Shift_L+T', # Open new terminal tab
         'sleep '+str(BASHRC_TIME),         # Bash command wait a sec
         'xdotool type "cd ~/askubuntu"',   # Change to working directory
         'xdotool key Return',              # Enter Key
         'title Ask\ Ubuntu',               # Window title (escape spaces)
         'gedit',                           # Last 5 files will open up
         'move x y',                        # Move windows to x and/or y

'move 3849 2266', # triple monitor setup

       ]

""" NOTE: To discover window coordinates, arrange on desktop and type:

    wmctrl -lG

"""

def process_commands(command_list):

for command in command_list:

    if command.endswith('&'):
        # Launch in background and get window ID opened
        active_pid, active_win = launch_command(command)
        if active_pid == 0:
            print("ERROR launching", command, \
            "Aborting 'dellstart' script")
            exit()

    elif command.startswith('move'):
        move_window(command, active_win)

    elif command.startswith('title'):
        terminal_title(command)

    elif command.startswith('gedit'):
        gedit()

    else:
        run_and_wait(command)


def launch_command(ext_name): ''' Launch external command in background and return PID to parent. Use for programs requiring more than .2 seconds to run. '''

all_pids = get_pids(ext_name)       # Snapshot current PID list
all_wins = get_wins(all_pids)       # Snapshot of windows open
new_pids = all_pids
new_wins = all_wins
sleep_count = 0                     # Counter to prevent infinite loops

os.popen(ext_name)                  # Run command in background

while new_pids == all_pids:         # Loop until new PID is assigned
    new_pids = get_pids(ext_name)   # Snapshot current PID list
    if sleep_count > 0:             # Don't sleep first time through loop
        time.sleep(.005)            # sleep 5 milliseconds
    sleep_count += 1
    if sleep_count == 1000:         # 10 second time-out
        print('launch_ext_command() ERROR: max sleep count reached')
        print('External command name:',ext_name)
        return 0

pid_list = list(set(new_pids) - set(all_pids))
if not len(pid_list) == 1:
    print('launch_command() ERROR: A new PID could not be found')
    return 0, 0

time.sleep(WINDOW_TIME)             # Give time for window to appear
new_wins = get_wins(all_pids)       # Snapshot of windows open
win_list = list(set(new_wins) - set(all_wins))
if not len(win_list) == 1:
    #print('launch_command() ERROR: New Window ID could not be found')
    #suppress error message because we aren't using window ID at all
    return int(pid_list[0]), 0

# Return PID of program we just launched in background
return int(pid_list[0]), int(win_list[0])


def run_and_wait(ext_name): ''' Launch external command and wait for it to end. Use for programs requiring less than .2 seconds to run. '''

result = os.popen(ext_name).read().strip()
#print('run_and_wait() command:', ext_name)
return result


def get_pids(ext_name): ''' Return list of PIDs for program name and arguments Whitespace output is compressed to single space ''' all_lines = [] # Just grep up to first space in command line. It was failing on ! prog_name = ext_name.split(' ',1)[0] all_lines = os.popen("ps aux | grep -v grep | grep " +
"'" + prog_name + "'").read().strip().splitlines PID = [] for l in all_lines(): l = ' '.join(l.split()) # Compress whitespace into single space PID.append(int(l.split(' ', 2)[1]))

return PID


def get_wins(all_pids): ''' Return list of all windows open under PID list Currently unncessary because we work on active window ''' windows = [] for pid in all_pids: all_lines = os.popen('xdotool search --pid ' + str(pid)).
read().strip().splitlines for l in all_lines(): windows.append(int(l))

return windows


def move_window(line, active_win): ''' Move window to x y coorindates on Desktop

    If the letter x or y is passed, that dimension remains unchanged eg:

        xdotool getactivewindow windowmove 100 100    # Moves to 100,100
        xdotool getactivewindow windowmove x 100      # Moves to x,100
        xdotool getactivewindow windowmove 100 y      # Moves to 100,y

'''
line = ' '.join(line.split())       # Compress whitespace to single space
x = line.split(' ')[-2]
y = line.split(' ')[-1]

# We don't need to pass window ID as last active window defaults
all_lines = os.popen('xdotool getactivewindow windowmove ' + x + ' ' + y). \
                     read().strip().splitlines
for l in all_lines():
    print(l)


def terminal_title(new_title): ''' Rather awkward calling xdotool which chokes on double quotes and bash via python which chokes on backslashes.

    Simple format (if it worked) would be:
        command = r'PS1="${PS1/\\u@\\h: \\w/' + title + '}"'

    The bash function copied from is:
        function termtitle() { PS1="${PS1/\\u@\\h: \\w/$@}"; }

    Reference for xdotool keycodes: 
    https://gitlab.com/cunidev/gestures/-/wikis/xdotool-list-of-key-codes
'''

title = new_title.split(' ', 1)[1]   # Strip out leading "title" token

command = 'xdotool type PS1='
run_and_wait(command)
run_and_wait('xdotool key quotedbl')
command = 'xdotool type $'
run_and_wait(command)
run_and_wait('xdotool key braceleft')
command = 'xdotool type PS1/'
run_and_wait(command)
run_and_wait('xdotool key backslash')
run_and_wait('xdotool key backslash')
command = 'xdotool type u@'
run_and_wait(command)
run_and_wait('xdotool key backslash')
run_and_wait('xdotool key backslash')
command = 'xdotool type "h: "'
run_and_wait(command)
run_and_wait('xdotool key backslash')
run_and_wait('xdotool key backslash')
command = 'xdotool type "w/"'
run_and_wait(command)
command = 'xdotool type "' + title + '"'
run_and_wait(command)
run_and_wait('xdotool key braceright')
run_and_wait('xdotool key quotedbl')
run_and_wait('xdotool key Return')


def gedit():

last_modified_files = gedit_recent_files()
command = 'gedit '
for f in last_modified_files:
    command=command+'"'
    command=command+f
    command=command+'" '
# Open gedit with last five modfied files
command=command+' &'
active_pid, active_win = launch_command(command)
if active_pid == 0:
    print("ERROR launching", command, \
    "Aborting 'dellstart' script")
    exit()


def gedit_recent_files(): ''' Get list of gedit 5 most recent files:

grep --no-group-separator -B5 'group>gedit' ~/.local/share/recently-used.xbel | sed -n 1~6p | sed 's# <bookmark href="file:///#/#g' | sed 's/"//g'

/home/rick/python/mmm added=2020-05-02T15:34:55Z modified=2020-11-19T00:43:45Z visited=2020-05-02T15:34:56Z> /home/rick/python/mserve added=2020-07-26T16:36:09Z modified=2020-11-28T01:57:19Z visited=2020-07-26T16:36:09Z>

'''
command = &quot;grep --no-group-separator -B5 'group&gt;gedit' &quot; + \
          &quot;~/.local/share/recently-used.xbel | &quot; + \
          &quot;sed -n 1~6p | sed 's#  &lt;bookmark href=&quot; + '&quot;' + \
          &quot;file:///#/#g' | &quot; + &quot;sed 's/&quot; + '&quot;' + &quot;//g'&quot;

recent_files = []
times = []
all_lines = os.popen(command).read().strip().splitlines
uniquifier = 1                  # gedit can give all open files same time
for l in all_lines():
    fname = l.split(' added=', 1)[0]
    trailing = l.split(' added=', 1)[1]
    modified = trailing.split(' modified=', 1)[1]
    modified = modified.split('Z', 1)[0]
    # TODO: 2038
    d = time.strptime(modified, '%Y-%m-%dT%H:%M:%S')
    epoch = time.mktime(d)
    epoch = int(epoch)
    recent_files.append(fname)

    try:
        times.index(epoch)
        # gedit has given multiple files the same modification time
        epoch += uniquifier
        uniquifier += 1
    except:
        pass                    # Not a duplicate time
    times.append(epoch)

N=5
top_files = []
if N &gt; len(times):
    # Less than 5 most recent files in list
    N = len(times)
    if N == 0:
        # No most recent files in list
        return top_files            # return empty list

# Store list in tmp to retrieve index
tmp=list(times)
# Sort list so that largest elements are on the far right
times.sort()

#print ('5 most recent from lists and indices')
for i in range(1, N+1):
    top_files.append(recent_files[tmp.index(times[-i])])

return top_files


if name == "main":

process_commands(commands)

end of dellstart


Note you may have to tinker with the variable BASHRC_TIME on your system to make program run faster. I have a lot of functions running in my ~/.bashrc and yours may run a lot faster.

I've planned on writing this for many years but never got around to it until now.

1

I got to chain multiple tabs like this, while avoiding warnings caused by using --command, which will be deprecated soon:

gnome-terminal\
    --tab\
        --title="TAB 1" -- bash -c "cd ~; ls; $SHELL"
gnome-terminal\
    --tab\
        --title="TAB 2" -- bash -c "cd ~/Desktop; ls; $SHELL"\
1

commenting for the answer by Lekensteyn. I know this is a old post, but for anyone who finds this useful (as I just did) Instead of making another "hacky script" with just put a function inside the script you are calling

hacky_function()
{
"$@"
exec "$SHELL"
}

Call your script with "x-terminal-emulator -e /path/to/script hacky_function optional arguments here"

Don't forget to put "$@" at the end of the script


Update to what I use now (2022)

#!/usr/bin/expect -f
# Get a Bash shell 
spawn -noecho bash
# Wait for a prompt 
expect "$ "
send "sudo /home/user/script.sh arg"
# Hand over control to the user
interact
exit
Sruli
  • 355
  • 3
  • 8
  • I couldn't get this to work. Do you mind providing a bit more detail? – Hiccup Jan 27 '22 at 21:09
  • 1
    @Hiccup the original answer by Lekensteyn required 2 separate scripts, adding the function to the script thus having only 1 script, i do not use this anymore, instead i use expect (updated the answer for this) – Sruli Jan 30 '22 at 14:16
-3

Use the screen command and -d detach from an existing screen session, and reattach here -m force a new screen session -S create a named session instead of using the default name