-
Notifications
You must be signed in to change notification settings - Fork 2
/
tmux-parallel-exec
executable file
·242 lines (215 loc) · 10.1 KB
/
tmux-parallel-exec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#!/bin/bash
# Author: Gokberk Cinbis, May 2020
set -e
# set environement variables to default values
FOO=${TMUX_PARALLEL_MAX_PANES_PER_WINDOW:=9}
function __uniqueid__()
{
# date, time, process info
local d=`command date +%Y%m%d`
local t=`command date +%H-%M-%S` # hour.minute.seconds (dots are useful for time, otherwise very unreadible)
#local host=`hostname`
local pid="$$"
command echo tmux-parallel-exec_${d}_${t}_${pid}
}
function __new_pane_or_window__() # create a new pane or a window
{
if [ $cmds_so_far -gt 0 ]; then # create a new pane/window if this is not the very first one
if (($cmds_so_far > 0)) && ! (($cmds_so_far % $TMUX_PARALLEL_MAX_PANES_PER_WINDOW)); then
command echo "command tmux new-window -t ${session_name}" >> "$driver" # new window
else
command echo "command tmux split-window -v -t ${session_name}" >> "$driver" # new pane
command echo "command tmux select-layout -t ${session_name} tiled" >> "$driver" # do this after every pane to create space for a new pane
fi
fi
}
function __increment_argi__() # just increment argi variable
{
argi=$((argi+1))
}
function __decrement_argi__() # just decrement argi variable
{
argi=$((argi-1))
}
function __increment_cmds_so_far__() # just increment cmds_so_far
{
cmds_so_far=$((cmds_so_far+1))
}
function __set_curwrapper_path__() # set the global path variable according to cmds_so_far
{
curwrapper="${outdir}/cmd${cmds_so_far}"
}
function __init_driver_script__()
{
command echo "command tmux new -d -s ${session_name}" > "$driver" # create detached session
command echo "echo ${session_name}" >> "$driver" # driver will print the session name
}
function __new_cmd__() # handle <cmdj> <dirj> etc.
{
if [ $cmds_so_far -eq 0 ]; then
__init_driver_script__
fi
__new_pane_or_window__ # before incrementing cmds_so_far
__increment_cmds_so_far__
__set_curwrapper_path__
command echo "command tmux send-keys -t ${session_name} \"command source $curwrapper\" C-m" >> "$driver" # tell tmux to read this script in the current pane
__increment_argi__ # now argi points to new argument
}
# -- obsolete. now not needed since we're supporting multiple lines. one can easily write a cd command. --
#function __write_pathJ__()
#{
# command echo "cd '$1'" >> "$curwrapper" # change directory
#}
function __cleanup__()
{
command rm -rf "$outdir"
exit 1
}
function __print_help__ ()
{
command echo "tmux-parallel-exec <option-flags> -c <cmd1_1> <cmd1_2> ... -c <cmd2_1> <cmd2_2> ... -c <cmdN_1> ..."
command echo ""
command echo "Run N commands in parallel (optionally within specified locations) via tmux by generating a tmux session and running"
command echo "commands in separate panes & windows. Creates a new window every TMUX_PARALLEL_MAX_PANES_PER_WINDOW (default 9)"
command echo "panes to avoid too small panes. This is the most advanced and most complex of all tmux-parallel-* tools."
command echo ""
command echo "Automatically generates a tmux session name using current date/time and PID. The session name is printed on screen once the session is created."
command echo ""
command echo ""
command echo "INPUT FLAGS"
command echo "--pause After completing the command execution, show a pause message which closes the pane on enter press. (default)"
command echo "--close Automatically close a pane if the command has been executed without errors."
command echo "--interactive On completion, leave the pane open and remain in shell."
command echo "--session-name <name> Use <name> instead of the auto-generated session name."
command echo "--session-prefix <name> Use <prefix> in the auto-generated session name."
command echo "-c <cmdJ1> <cmdJ2>... Run the series of commands <cmdJ1> <cmdJ2> ... consecutively, starting in the current directory. "
command echo " The arguments are considered as commands until a new argument starts with -"
command echo ""
command echo "HINTS & NOTES"
command echo "* The commands are run in the default terminal loaded by tmux. The exit handling is done via bash commands. If you are using a bash-incompatible shell"
command echo " you may want to use --interactive flag."
command echo ""
command echo "* You may pass arguments within command strings and you may benefit from watch utility. Example:"
command echo ' tmux-parallel-exec -c "ls /" -c "ls $HOME" -c "uptime" "uname -a" -c "watch date"'
command echo ""
command echo "* You can easily change directory before running a command. Example:"
command echo ' tmux-parallel-exec -c "cd /" "pwd" -c "cd $HOME" "pwd"'
command echo ""
command echo "* You may create simple loops as commands. Example:"
command echo " tmux-parallel-exec -c 'echo FAST' 'while true; do echo w1; sleep 1; done' -c 'echo SLOW' 'while true; do echo w2; sleep 3; done'"
command echo ""
command echo "* To avoid syntax and readability problems, commands are not echoed but you can easily add your own echo statements."
command echo ""
command echo "SEE tmux-parallel-exec tmux-parallel-multidir"
exit 1
}
function __write_cmdJ__()
{
# do not echo: command echo "echo $1" >> "$curwrapper" # echo the command to be executed
command echo "$1" >> "$curwrapper" # run command
}
function __handle_finish_states__() # add the exit case handle to each per-pane script
{
cmds_final=$cmds_so_far
for (( cmds_so_far=1; cmds_so_far<=$cmds_final; cmds_so_far++ )); do
__set_curwrapper_path__
if [[ "${pane_exit_mode}" == 'pause' ]]; then
command echo 'status=$?' >> "$curwrapper"
command echo 'if [ $status -eq 0 ]; then read -p "Press enter to continue"; exit; fi' >> "$curwrapper"
elif [[ "${pane_exit_mode}" == 'close' ]]; then
command echo 'status=$?' >> "$curwrapper"
command echo 'if [ $status -eq 0 ]; then exit; fi' >> "$curwrapper"
else # interactive
foo="" # do nothing , do not even write status.
fi
done
}
function __main__()
{
local narg=$#
if [ $narg -lt 1 ]
then
__print_help__
exit 1
fi
# design notes:
# * we'll create one temporary script per command and one driver script that sends tmux commands.
# * the driver script will create panes and send commands to tmux to run (via sourcing) the corresponding temporary command script in each pane.
# * the temporary command scripts will help us handling exit cases. in the future we might use this infrastructure to allow multi-line scripts as well.
# * it could have been possible to create one common temporary script but it had the following risk: if one
# command goes wrong, then the whole script might be broken.
# init/set global variables
session_name=`__uniqueid__`
pane_exit_mode='pause' # pause | close | interactive
outdir=`command mktemp -d` # temp script, calls tmux options
driver="$outdir/tmux-parallel-driver-temp" # temp script, calls tmux options
cmds_so_far=0
argi=0 # argument reading index
curwrapper="" # for the J-th argument, the temp command script is $outdir/cmdJ.
debug_mode=0 # set to 1 to debug the script
# initialize warpper script. this will contain a series of functions with exit condition handlers
command echo "#!/bin/bash" > "$driver"
# dont do this, run the driver as much as you can, keep going: echo "set -e" > "$driver"
for (( argi=1; argi<=$narg; argi++ )); do
if [ "${!argi}" == '--pause' ]; then
pane_exit_mode='pause'
elif [[ "${!argi}" == '--close' ]]; then
pane_exit_mode='close'
elif [[ "${!argi}" == '--interactive' ]]; then
pane_exit_mode='interactive'
elif [[ "${!argi}" == '--session-name' ]]; then
__increment_argi__
if [ $cmds_so_far -gt 0 ]; then
>&2 echo "--session-name argument must precede all -c arguments."
__cleanup__
fi
session_name="${!argi}"
elif [[ "${!argi}" == '--session-prefix' ]]; then
__increment_argi__
if [ $cmds_so_far -gt 0 ]; then
>&2 echo "--session-prefix argument must precede all -c arguments."
__cleanup__
fi
session_name="${!argi}${session_name}"
elif [[ "${!argi}" == '-c' ]]; then
__new_cmd__
# keep reading a new command until -... is reached
for (( ; argi<=$narg; argi++ )); do
if [[ ${!argi:0:1} == "-" ]] ; then
__decrement_argi__
break
else
__write_cmdJ__ "${!argi}"
fi
done
else
command echo "Unrecognized argument: {!argi}"
__print_help__
__cleanup__
fi
done
# attach commands
# do not automatically clean the temporary folder if the attachment has failed (as it tends to delete files before they are loaded
command echo "if command tmux a -t ${session_name};" >> "$driver" # attach
command echo "then command rm -rf \"$outdir\";" >> "$driver"
command echo "else command echo 'Cannot attach to tmux, run the following manually:'" >> "$driver"
command echo " command echo tmux a -t ${session_name}" >> "$driver"
command echo " command echo rm -rf \"$outdir\"" >> "$driver"
command echo "fi" >> "$driver"
__handle_finish_states__
command chmod 755 "$driver"
# run the driver script
if [[ $debug_mode == 1 ]]; then
command echo "scripts have been written to $outdir" # just for debug
else
"$driver"
fi
}
# ok with multiple quoted arguments
__main__ "$@"
exit 0
# IMPLEMENTATION NOTES
# -- do not use the following single-line tmux call idea:
# tmux new -s session \; send-keys a C-m \; ...
# this approach makes it hard to debug & understand when something goes wrong
# therefore, instead, first create the session in detached mode, send keys to it and then attach to it