forked from MartinRamm/fzf-docker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdocker-fzf
executable file
·619 lines (519 loc) · 19.1 KB
/
docker-fzf
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
#!/bin/bash
DOCKER_BIN=${DOCKER_BIN:-docker}
DOCKER_COMPOSE_BIN=${DOCKER_COMPOSE_BIN:-docker-compose}
# $1 (Optionally) - if present (value doesn't matter), this test checks if any containers exist. Else, if active (running) containers exis
__docker_pre_test() {
if [[ -z "$1" ]] && [[ $($DOCKER_BIN ps --format '{{.Names}}') ]]; then
return 0;
fi
if [[ ! -z "$1" ]] && [[ $($DOCKER_BIN ps -a --format '{{.Names}}') ]]; then
return 0;
fi
echo "No containers found";
return 1;
}
__docker_images_pre_test() {
if [[ $($DOCKER_BIN images -qa) ]]; then
return 0;
fi
echo "No images found"
return 1;
}
#1 (Optional) path to `docker-compose.yml` file
__docker_compose_pre_test() {
if [ -z "$1" ]; then
if [[ $($DOCKER_COMPOSE_BIN config --services) ]]; then
return 0;
fi
echo "No docker-compose.yml found (or it contains errors). You can pass as the first argument a path to the service declaration file."
return 1;
fi
if [[ $($DOCKER_COMPOSE_BIN --file $1 config --services) ]]; then
return 0;
fi
echo "Invalid service declaration file $1."
return 1;
}
# $1: time interval - e.g.: `1m` for 1 minute
# $2: names of container(s) to display logs from
__docker_logs() (
local since=""
if [ ! -z "$1" ]; then
since="--since $1 "
fi
local count=$(wc -l <<< $2)
if [[ -z "$2" ]]; then
return 1
fi
if [[ "$count" -eq "1" ]]; then
eval "$DOCKER_BIN logs -f $since$2"
return 0
fi
local resetColor="\x1b[39m\x1b[49m"
#list of 48 distinct colors
local allColors="\x1b[90m\n\x1b[92m\n\x1b[93m\n\x1b[94m\n\x1b[95m\n\x1b[96m\n\x1b[97m\n\x1b[30m\n\x1b[31m\n\x1b[32m\n\x1b[33m\n\x1b[34m\n\x1b[35m\n\x1b[36m\n\x1b[40m\x1b[90m\n\x1b[40m\x1b[91m\n\x1b[40m\x1b[92m\n\x1b[40m\x1b[94m\n\x1b[40m\x1b[95m\n\x1b[40m\x1b[96m\n\x1b[40m\x1b[97m\n\x1b[41m\x1b[90m\n\x1b[41m\x1b[93m\n\x1b[41m\x1b[95m\n\x1b[41m\x1b[97m\n\x1b[42m\x1b[90m\n\x1b[42m\x1b[93m\n\x1b[42m\x1b[97m\n\x1b[43m\x1b[90m\n\x1b[43m\x1b[93m\n\x1b[43m\x1b[97m\n\x1b[44m\x1b[91m\n\x1b[44m\x1b[92m\n\x1b[44m\x1b[93m\n\x1b[44m\x1b[95m\n\x1b[44m\x1b[97m\n\x1b[45m\x1b[93m\n\x1b[45m\x1b[97m\n\x1b[46m\x1b[90m\n\x1b[46m\x1b[91m\n\x1b[46m\x1b[92m\n\x1b[46m\x1b[93m\n\x1b[46m\x1b[96m\n\x1b[46m\x1b[97m\n\x1b[47m\x1b[90m\n\x1b[47m\x1b[95m\n\x1b[47m\x1b[96m\n"
#list of `$count` number of distinct colors
local colors=$(echo -e "$allColors" | shuf -n $count)
local allPids=()
local writeToTmpFilePids=()
local tmpFile="/tmp/fzf-docker-logs-$(date +'%s')"
function _exit {
for pid in "${allPids[@]}"; do
# ignore if process is not alive anymore
kill -9 $pid > /dev/null 2> /dev/null
done
test -e $tmpFile && rm -f $tmpFile
}
trap _exit INT TERM SIGTERM
while read -r name; do
# last color from list
local color=$(echo -e "$colors" | tail -n 1)
# update list - remove last color from list
colors=$(echo -e "$colors" | head -n -1)
# in bash, to get the pid for `docker logs` (so we can kill it in _exit), use `command1 > >(command2)` instead of `command1 | command2` - see https://stackoverflow.com/a/8048493/2732818
# sed -u needed as explained in https://superuser.com/a/792051
eval "$DOCKER_BIN logs --timestamps -f $since\"$name\" 2>&1 > >(sed -u -e \"s/^/${color}[${name}]${resetColor} /\" >> $tmpFile) &"
local pid=($!)
allPids+=($pid)
writeToTmpFilePids+=($pid)
done <<< "$2" # bash executes while loops in pipe in subshell, meaning pids will not be available outside of loop when using `echo -e "$2" | while...`
#wait for all historc logs being written into $tmpFile
sleep 2
local removeTimestamp='sed -r -u "s/((\x1b\[[0-9]{2}m){0,2}\[.*\]\x1b\[39m\x1b\[49m )[^ ]+ /\1/"'
#sort historic logs
local numOfLines=$(wc -l < $tmpFile)
eval "head -n $numOfLines $tmpFile | sort --stable --key=2 | $removeTimestamp"
#show new logs
local numOfLines=$((numOfLines+1))
#2>/dev/null because "tail: /tmp/fzf-docker-logs: file truncated" is outputed every time $tmpFile is emptied
eval "tail -f -n +$numOfLines $tmpFile > >($removeTimestamp) 2>/dev/null &"
allPids+=($!)
#we don't really need to keep the logs on the hdd in $tmpFile so every minute empty it. But keep one line, so the "tail -f" can keep track
eval "while true; do tail -n 1 $tmpFile > $tmpFile; sleep 10s; done &"
allPids+=($!)
#wait for all docker containers have stoped, i.e. no more logs can be generated
for pid in "${writeToTmpFilePids[@]}"; do
wait $pid
done
#this also kills the `tail -f $tmpFile` process
_exit
)
__docker_compose_fileref_generator() {
if [ ! -z "$1" ]; then
echo "--file $1"
fi
}
#1 (Optional) path to `docker-compose.yml` file
#2 A regexp that needs to be present in the docker-compose.yml file for this command to return it
__docker_compose_parse_services_config() {
local fileref=$(__docker_compose_fileref_generator $1)
# `docker-compose config` normalizes the indentation and format, no matter what the actual file is...
local config=$(eval "$DOCKER_COMPOSE_BIN $fileref config")
local inServicesBlock='false'
local service=''
echo -e "$config" \
| while IFS= read -r line; do
if [[ "$line" == "services:" ]]; then
inServicesBlock='true'
elif [[ "$line" =~ ^[^:" "]+:.*$ ]]; then
inServicesBlock='false'
fi
if [[ "$inServicesBlock" == "true" ]]; then
if [[ "$line" =~ ^" "{2}[^:" "]+:$ ]]; then
local service=$(echo -e "$line" | grep -o '[^: ]\+')
fi
if [[ "$line" =~ $2 ]] && [[ ! -z "service" ]]; then
echo "$service"
service=''
fi
fi
done
}
#docker restart
dr() {
__docker_pre_test
if [ $? -eq 0 ]; then
local containers=$($DOCKER_BIN ps --format '{{.Names}}' | fzf -m)
echo -e "$containers" \
| while read -r name; do
echo "Restarting $name..."
$DOCKER_BIN restart $name
done
__docker_logs "1m" "$containers"
fi
}
#docker logs
dl() {
__docker_pre_test "all"
if [ $? -eq 0 ]; then
local containers=$($DOCKER_BIN ps -a --format '{{.Names}}' | fzf -m)
__docker_logs "$1" "$containers"
fi
}
#docker logs all
dla() {
__docker_pre_test "all"
if [ $? -eq 0 ]; then
local containers=$($DOCKER_BIN ps -a --format '{{.Names}}')
__docker_logs "$1" "$containers"
fi
}
#docker exec
de() {
__docker_pre_test
if [ $? -eq 0 ]; then
local name=$($DOCKER_BIN ps --format '{{.Names}}' | fzf)
if [ ! -z "$name" ]; then
local command="$1"
if [ -z "$command" ] && [ -f "$HOME/.docker-fuzzy-search-exec" ]; then
command=$($HOME/.docker-fuzzy-search-exec "$name")
fi
if [ -z "$command" ]; then
local imageName=$($DOCKER_BIN inspect --format '{{.Config.Image}}' $name | sed -e 's/:.*$//g') #without version
case "$imageName" in
"mysql" | "bitnami/mysql" | "mysql/mysql-server" | "percona" | centos/mysql*)
command='mysql -uroot -p$MYSQL_ROOT_PASSWORD'
;;
"mongo" | "circleci/mongo")
command='if [ -z "$MONGO_INITDB_ROOT_USERNAME" ]; then mongo; else mongo -u "$MONGO_INITDB_ROOT_USERNAME" -p "$MONGO_INITDB_ROOT_PASSWORD"; fi'
;;
"bitnami/mongodb")
command='if [ ! -z "$MONGODB_ROOT_PASSWORD" ]; then mongo -u root -p "$MONGODB_ROOT_PASSWORD"; elif [ ! -z $MONGODB_USERNAME ]; then mongo -u "$MONGODB_USERNAME" -p "$MONGODB_PASSWORD" "$MONGODB_DATABASE"; else mongo; fi'
;;
centos/mongodb*)
command='mongo -u "admin" -p "$MONGODB_ADMIN_PASSWORD" --authenticationDatabase admin'
;;
"redis" | "circleci/redis" | "bitnami/redis" | centos/redis*)
command='echo -n "Enter DB Number to connect to (^[1-9][0-9]?$): " && read dbNum && redis-cli -n $dbNum'
if [[ "$imageName" == "bitnami/redis" ]] || [[ "$imageName" == "centos/redis"* ]]; then
command="if [ -z \$REDIS_PASSWORD ]; then $command; else $command -a \"\$REDIS_PASSWORD\"; fi"
fi
;;
*)
command='command $(command -v zsh || command -v bash || command -v ash || command -v sh)'
esac
command="sh -c '$command'"
fi
eval "$DOCKER_BIN exec -it $name $command"
fi
fi
}
#docker remove
drm() {
__docker_pre_test "all" \
&& $DOCKER_BIN ps -aq --format "{{.Names}}" \
| fzf -m \
| while read -r name; do
$DOCKER_BIN rm -f $name
done
}
#docker remove all
drma() {
__docker_pre_test "all" \
&& $DOCKER_BIN rm $($DOCKER_BIN ps -aq) -f
}
#docker stop
ds() {
__docker_pre_test \
&& $DOCKER_BIN ps --format '{{.Names}}' \
| fzf -m \
| while read -r name; do
$DOCKER_BIN update --restart=no $name
$DOCKER_BIN stop $name
done
}
#docker stop all
dsa() {
__docker_pre_test
if [ $? -eq 0 ]; then
$DOCKER_BIN update --restart=no $($DOCKER_BIN ps -q)
$DOCKER_BIN stop $($DOCKER_BIN ps -q)
fi
}
#docker stop
dsrm() {
__docker_pre_test \
&& $DOCKER_BIN ps --format '{{.Names}}' \
| fzf -m \
| while read -r name; do
$DOCKER_BIN update --restart=no $name
$DOCKER_BIN stop $name
$DOCKER_BIN rm -f $name
done
}
#docker stop all
dsrma() {
dsa
drma
}
#docker kill
dk() {
__docker_pre_test \
&& $DOCKER_BIN ps --format '{{.Names}}' | fzf -m --print0 \
| fzf -m \
| while read -r name; do
$DOCKER_BIN update --restart=no $name
$DOCKER_BIN kill $name
done
}
#docker kill
dka() {
__docker_pre_test
if [ $? -eq 0 ]; then
$DOCKER_BIN update --restart=no $($DOCKER_BIN ps -q)
$DOCKER_BIN kill $($DOCKER_BIN ps -q)
fi
}
#docker kill
dkrm() {
__docker_pre_test \
&& $DOCKER_BIN ps --format '{{.Names}}' \
| fzf -m \
| while read -r name; do
$DOCKER_BIN update --restart=no $name
$DOCKER_BIN kill $name
$DOCKER_BIN rm -f $name
done
}
#docker kill
dkrma() {
dka
drma
}
#docker remove image
drmi() {
__docker_images_pre_test;
if [ "$?" -eq "0" ]; then
echo "Loading images. Depending on the number of images you have, this may take a while...";
#column1: comma seperated list of ids to be removed if this option is selected
#column2: text to be displayed to the user.
#seperator: tab ("\t")
local images='';
# create image references for non-dangling images
for id in $($DOCKER_BIN images --format "{{.ID}}" --filter "dangling=false" | sort | uniq); do #remove duplicate ids. necessary because each image tag has an entry, and images may have multiple tags
local references=$($DOCKER_BIN inspect "$id" --format "{{range .RepoTags}}{{.}}\n{{end}}{{range .RepoDigests}}{{.}}\n{{end}}");
local list=$(echo "$references" | sed 's/\\n$//' | sed -r 's/\\n/\n/g'); #remove trailing new line && replace \n with actual new line
local listLength=$(echo "$list" | wc -l);
local head=$(echo "$list" | head -n 1);
local tail=$(echo "$list" | tail -n 1);
local result="$id\t";
for ref in $(echo -e "$list"); do
if [[ "$head" == "$ref" ]]; then
result+="$ref";
if [[ "$listLength" == "2" ]]; then
result+=" (alias: ";
elif [[ "$listLength" != "1" ]]; then
result+=" (aliases: ";
fi;
elif [[ "$tail" == "$ref" ]]; then
result+="$ref)";
else
result+="$ref, ";
fi;
done;
if [[ "$images" == "" ]]; then
images="$result";
else
images+="\n$result";
fi
done;
# create image references for dangling images
local ids=$($DOCKER_BIN images --format "{{.ID}}" --filter "dangling=true");
local numberOfIds=$(echo "$ids" | wc -l)
if [[ "$numberOfIds" != "0" ]]; then
#add option to remove all dangling images
local allDanglingImages=$(echo -e "$ids" | sed ':a; N; s/\n/,/; ta'); # join all ids into CSV list
allDanglingImages+="\tAll dangling images (total of $numberOfIds)";
if [[ "$images" == "" ]]; then
images="$allDanglingImages";
else
images+="\n$allDanglingImages";
fi
#add options, group by label
local idCreatedLabel='';
for id in $(echo "$ids" | head -n 20); do
local created=$($DOCKER_BIN inspect "$id" --format "{{.Created}}");
local labels=$($DOCKER_BIN inspect "$id" --format '{{ range $key, $value := .Config.Labels }}{{ $key }}:{{$value}}\n{{end}}');
while read -r label; do #also entered for no label
local reference=$(echo -e "$id\t$created\t$label");
if [[ "$idCreatedLabel" == "" ]]; then
idCreatedLabel="$reference";
else
idCreatedLabel+="\n$reference";
fi
done <<< "$(echo "$labels" | sed 's/\\n$//' | sed -r 's/\\n/\n/g')"; #remove trailing new line && replace \n with actual new line
done;
#$1=id
#$2=created date
#$3=label
#a = all ids that belong to label
#b = max created date
#c = max created id
# shellcheck disable=SC2016
local awk='
{
a[$3] = a[$3] ? a[$3]","$1 : $1; #create a CSV list of ids (grouped by label)
if( !b[$3] || b[$3] < $2 ) { #if the current row created date is bigger than all dates in previous rows
b[$3] = $2; #set date
c[$3] = $1; #set id
}
} END {
for ( i in a ) {
print (i ? i : "none") #replace no label with "none" here, as otherwise the splitting of columns didnt work below
"\t"
a[i]
"\t"
c[i];
}
}'
while read -r labelIdsLatestId; do
local label="$(echo -e "$labelIdsLatestId" | cut -f 1)"
local allIds="$(echo -e "$labelIdsLatestId" | cut -f 2)"
local numberOfIds="$(echo "$allIds" | awk -F ',' '{print NF}')"
local latestBuildId="$(echo -e "$labelIdsLatestId" | cut -f 3)"
local labelReference="";
if [[ "$label" == "none" ]]; then #see awk. since labels always contain key and value (seperated by a colon), this is safe
labelReference='that have no label';
else
labelReference="that have the label $label";
fi;
local imagesPluralOrSingular="image"
if [[ "$numberOfIds" != "1" ]]; then
imagesPluralOrSingular+="s";
fi;
images+="\n$allIds\tAll dangling $imagesPluralOrSingular (total of $numberOfIds) $labelReference"
if [[ "$numberOfIds" != "1" ]]; then
local idsWithoutLatest="$(echo "$allIds" | sed -r "s/(^$latestBuildId,|,$latestBuildId)//")"
imagesPluralOrSingular="image"
if [[ "$numberOfIds" != "2" ]]; then #2 because the number of ids still includes the id to keep (newest created date)
imagesPluralOrSingular+="s";
fi;
images+="\n$idsWithoutLatest\tAll EXCEPT THE NEWEST dangling $imagesPluralOrSingular (total of $((numberOfIds-1))) $labelReference"
fi;
done <<< "$(echo -e "$idCreatedLabel" | awk -F "\t" "$awk")"
fi;
#column1 contains ids. multiple ids per line may be comma seperated
#because there may be an overlap in dangling image ids, remove duplicates.
echo -e "$images" \
| fzf -m -d "\t" --with-nth=2 \
| cut -f 1 \
| sed 's/,/\n/g' \
| sort \
| uniq \
| while read -r idToRemove; do
$DOCKER_BIN rmi "$idToRemove"
done;
fi;
}
#docker remove all images
drmia() {
__docker_images_pre_test \
&& $DOCKER_BIN rmi $($DOCKER_BIN images -qa) -f
}
#docker clean
dclean() {
dsrma
drmia
}
#docker compose up
dcu() {
local fileref=$(__docker_compose_fileref_generator $1)
__docker_compose_pre_test $1 \
&& eval "$DOCKER_COMPOSE_BIN $fileref config --services" \
| fzf -m \
| while read -r service; do
eval "$DOCKER_COMPOSE_BIN $fileref up -d $service"
done
}
#docker compose up all
dcua() {
local fileref=$(__docker_compose_fileref_generator $1)
__docker_compose_pre_test $1 \
&& eval "$DOCKER_COMPOSE_BIN $fileref up -d"
}
#docker compose build
dcb() {
local fileref=$(__docker_compose_fileref_generator $1)
__docker_compose_pre_test $1 \
&& __docker_compose_parse_services_config "$1" '^ build:.*$' \
| fzf -m \
| while read -r service; do
eval "$DOCKER_COMPOSE_BIN $fileref build --force-rm --no-cache --pull $service"
done
}
#docker compose build all
dcba() {
local fileref=$(__docker_compose_fileref_generator $1)
__docker_compose_pre_test $1 \
&& eval "$DOCKER_COMPOSE_BIN $fileref build --force-rm --no-cache --pull --parallel"
}
#docker compose pull
dcp() {
local fileref=$(__docker_compose_fileref_generator $1)
__docker_compose_pre_test $1 \
&& __docker_compose_parse_services_config "$1" '^ image:.*$' \
| fzf -m \
| while read -r service; do
eval "$DOCKER_COMPOSE_BIN $fileref pull --ignore-pull-failures $service"
done
}
#docker compose pull all
dcpa() {
local fileref=$(__docker_compose_fileref_generator $1)
__docker_compose_pre_test $1 \
&& eval "$DOCKER_COMPOSE_BIN $fileref pull --ignore-pull-failures"
}
#docker compose update image
dcupd() {
local fileref=$(__docker_compose_fileref_generator $1)
__docker_compose_pre_test $1
if [ "$?" -eq "0" ]; then
local buildServices=$(__docker_compose_parse_services_config "$1" '^ build:.*$')
local pullServices=$(__docker_compose_parse_services_config "$1" '^ image:.*$')
eval "$DOCKER_COMPOSE_BIN $fileref config --services" \
| fzf -m \
| while read -r service; do
echo -e "$buildServices" \
| while read -r buildService; do
if [[ "$buildService" == "$service" ]]; then
eval "$DOCKER_COMPOSE_BIN $fileref build --force-rm --no-cache --pull $service"
continue 2;
fi
done
echo -e "$pullServices" \
| while read -r pullService; do
if [[ "$pullService" == "$service" ]]; then
eval "$DOCKER_COMPOSE_BIN $fileref pull --ignore-pull-failures $service"
continue 2;
fi
done
done
fi
}
#docker compose update image all services
dcupda() {
dcba "$1"
dcpa "$1"
}
fzf-docker-debug-info() {
location="${BASH_SOURCE[0]}"
if [ -z "$location" ]; then #fzf
location=$(type -a $0 | sed "s/$0 is a shell function from //g")
fi;
gitRepo="$(dirname $location)/.git"
commitHash=$(eval "git --git-dir $gitRepo rev-parse HEAD")
latestTag=$(eval "git --git-dir $gitRepo describe --tags")
fzfDockerVersion="$latestTag ($commitHash)"
shellEnvironment=$(ps p "$$" o cmd=)
shell=$(echo $shellEnvironment | grep -oE "^\S+")
shellVersion=$(eval "$shell --version")
fzfVersion=$(fzf --version)
dockerVersion=$($DOCKER_BIN --version)
dockerComposeVersion=$($DOCKER_COMPOSE_BIN --version)
dockerExecCustomDefaults=$(test -f "$HOME/.docker-fuzzy-search-exec" && cat "$HOME/.docker-fuzzy-search-exec" || echo "N.A.")
for v in "fzfDockerVersion" "shellEnvironment" "shellVersion" "fzfVersion" "dockerVersion" "dockerComposeVersion" "dockerExecCustomDefaults"; do
echo -e "${v}:"
v=$(eval "echo -e \"\$$v\"" | sed 's/^/\t/g')
echo -e "$v"
done;
}