1

Goal: I'm attempting to create an interactive version of docker ps. Basically, have each line be a "menu" such that a user can: start, stop, ssh, etc.

Example:

   CONTAINER ID        IMAGE             COMMAND                  CREATED             STATUS              PORTS                                   NAMES
1. bf4a9c7de6bf        app_1             "docker-php-entryp..."   7 days ago          Up About an hour    443/tcp, 0.0.0.0:80->80/tcp, 9000/tcp   app_1
2. 26195f0764ce        app_2             "sh /var/www/html/..."   10 days ago         Up About an hour    443/tcp, 127.0.0.1:8000->80/tcp         app_2

Upon choosing (1/2, etc) there will be an options menu to perform various actions on the selected container.

Problem: I can't seem to figure out how to parse out each line of the docker ps command such that i'll have the Container ID and other values as array elements.

The code so far:

list=`docker ps`
IFS=$'\n' array=($list)

for index in ${!array[@]}
do
  declare -a 'a=('"${array[index]}"')'
  printf "%s\n" "${a[@]}"  
done

The result:

CONTAINER
ID
IMAGE
COMMAND
CREATED
STATUS
PORTS
NAMES
/usr/bin/dockersh: array assign: line 9: syntax error near unexpected token `>'
/usr/bin/dockersh: array assign: line 9: `bf4a9c7de6bf        app_1             "docker-php-entryp..."   7 days ago          Up About an hour    443/tcp, 0.0.0.0:80->80/tcp, 9000/tcp   app_1'
andrew
  • 23
  • 6
  • I'm confused on the point of having the declare inside the loop? If it is always the 'a' array, why declare it more than once? Also, without seeing any data (as like others I do not have docker installed) it makes it hard to know a good answer to provide? – grail Feb 25 '17 at 17:57
  • Please take a look: http://www.shellcheck.net/ – Cyrus Feb 25 '17 at 18:03
  • @grail - The data is what you see in the Example block of my question. As for the declare statement, my thought was that I'm dealing with 2 arrays: 1. The array of lines 2. The array of "columns" on each line – andrew Feb 25 '17 at 23:32
  • thanks @Cyrus - it's been bookmarked :) – andrew Feb 25 '17 at 23:56

3 Answers3

3

It looks like you've got a few issues with the quoting, maybe try:

list=$(docker ps)
IFS=$'\n' array=($list)

for index in "${!array[@]}"
do
  declare -a a=("${array[index]}")
  printf "%s\n" "${a[@]}"  
done

Without proper quoting your string will be likely by re-split; consider checking your shell scripts @ shell-check.net, as it usually will give you some good hints regarding bad syntax.

l'L'l
  • 44,951
  • 10
  • 95
  • 146
  • Thanks; this worked as I expected it. Did not know about shell-check, but it certainly becoming part of this learning experience :) – andrew Feb 25 '17 at 23:41
  • @aniculescu: You're welcome! The site to check bash syntax is actually [shellcheck.net](http://shellcheck.net) (I accidentally hyphenated it for whatever reason), although it looks like you figured it out :) – l'L'l Feb 26 '17 at 00:12
  • no worries, i have it now in vim and sublime. but figured out the site without much issue. – andrew Feb 26 '17 at 00:31
1

If you want to have an associative array that features a matrix with all your docker ps field accessible in row/column, you can use awk to insert separator | between fields. Then export the result in a single associative array and build the matrix according to the number of column you expect (eg 7) :

#!/bin/bash
IFS=$'|'

data=$(docker ps -a | awk '
function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s }
{
    if (NR == 1) {
        head[1] = index($0,"CONTAINER ID")
        head[2] = image=index($0,"IMAGE")
        head[3] = command=index($0,"COMMAND")
        head[4] = created=index($0,"CREATED")
        head[5] = status=index($0,"STATUS")
        head[6] = ports=index($0,"PORTS")
        head[7] = names=index($0,"NAMES")
    }
    else{
        for (i = 1;i < 8;i++) {
            if (i!=7){
                printf "%s",rtrim(substr($0, head[i], head[i+1] - 1 - head[i])) "|"
            }
            else{
                printf "%s",rtrim(substr($0, head[i], 100)) "|"
            }
        }
        print ""
    }
}')

arr=($data)
max_column=7
row=0
column=0

declare -A matrix

for index in "${!arr[@]}"
do
    matrix[$row,$column]=$(echo "${arr[index]}" | tr -d '\n')
    column=$((column+1))
    if [ $((column%max_column)) == 0 ]; then
        row=$((row+1))
        column=0
    fi
done

echo "first  container ID   is : ${matrix[0,0]}"
echo "second container ID   is : ${matrix[1,0]}"
echo "third  container NAME is : ${matrix[2,6]}"

In the awk part, the aim is to insert a | character between each field for the data to be injected into an associative array with the | delimiter

As field content is aligned with field title, we store the index of each field names in head array and extract each field trimming according to the next field position

Then the matrix is build according to the max column count (7). Then each row/column can be accessed easily with ${matrix[row,column]}

Bertrand Martel
  • 42,756
  • 16
  • 135
  • 159
  • Thank You @bertrand-martel! A very thorough solution, happy to see a bunch of my next steps solved here. It's been interesting learning how to implement "simple" concepts in bash - i've done this in python and php in minutes, but the bash version has taken considerably longer – andrew Feb 25 '17 at 23:47
  • 1
    Maybe the concept of getting the index of the fields title like `IMAGE` etc... can be generalized if you want to support other command than `docker ps` like `docker service ls` in docker swarm which have different field names. check [this post](http://stackoverflow.com/questions/42399522/how-to-sort-by-name-docker-service-ls/42401534#42401534) – Bertrand Martel Feb 25 '17 at 23:55
  • 1
    For sure, the `ps` command was most definitely the beginning of this mild obsession :) – andrew Feb 25 '17 at 23:58
  • As a side note, for some reason the associative array flag (`-A`) for declare yields: `declare: -A: invalid option` My bash version: `GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin14)` – andrew Feb 26 '17 at 00:03
  • 1
    associative array is for bash 4+, you have some solutions [here](http://stackoverflow.com/questions/11776468/create-associative-array-in-bash-3) though, basically `use separate variables for each element, with a common naming prefix` – Bertrand Martel Feb 26 '17 at 00:08
0

Usual story ... don't read data with a for loop unless you know exactly the format and how to control it:

while IFS="\n" read -r line
do
  array+=("$line")
done< <(docker ps)

Personally I would try and remove the numbers from the start of the lines (1., 2., etc) because then you can throw it into a select and it will give you numbers which can then be used to reference the relevant items.

grail
  • 914
  • 6
  • 14