Archive for the ‘ Scripting ’ Category


Curl – network performance stats


Curl is a tool commonly used with transferring data from or to a client/server. Its portability has allowed it to present in most OS installations by default, and thus making as a standard tool for troubleshooting and debugging network or server connectivity issues.

Here you will see some Curl features to gather network connectivity performance metrics, all exposed with -w flag.

-w, --write-out <format>
Make curl display information on stdout after a completed transfer.

time_namelookup
 The time, in seconds, it took from the start until the name resolving was completed.

time_connect
 The time, in seconds, it took from the start until the TCP connect to the remote host  (or  proxy)  was completed.

time_pretransfer
 The  time, in seconds, it took from the start until the file transfer was just about to begin.

time_starttransfer
  The  time,  in  seconds,  it took from the start until the first byte was just about to be transferred.

time_total
  The total time, in seconds, that the full operation lasted.

With the above 5 options, we can get a decent amount of information on the culprit for network issue. Is it DNS resolution delay? Or delay in TCP 3 way handshake? etc


Examples

On first run –

$ curl -s -w '\nLookup time:\t%{time_namelookup}\nConnect time:\t%{time_connect}\nPreXfer time:\t%{time_pretransfer}\nStartXfer time:\t%{time_starttransfer}\n\nTotal time:\t%{time_total}\n' -o /dev/null https://www.fifa.com

Lookup time:	0.062836
Connect time:	0.078382
PreXfer time:	0.123689
StartXfer time:	0.155934

Total time:	0.182570

Second run –

$ curl -s -w '\nLookup time:\t%{time_namelookup}\nConnect time:\t%{time_connect}\nPreXfer time:\t%{time_pretransfer}\nStartXfer time:\t%{time_starttransfer}\n\nTotal time:\t%{time_total}\n' -o /dev/null https://www.fifa.com

Lookup time:	0.022473
Connect time:	0.036173
PreXfer time:	0.087416
StartXfer time:	0.134248

Total time:	0.171503

On second run, the dns look up time is lower, as it is coming from cache. Let us flush dns cache –

$ sudo systemd-resolve --flush-caches

$ curl -s -w '\nLookup time:\t%{time_namelookup}\nConnect time:\t%{time_connect}\nPreXfer time:\t%{time_pretransfer}\nStartXfer time:\t%{time_starttransfer}\n\nTotal time:\t%{time_total}\n' -o /dev/null https://www.fifa.com

Lookup time:	0.072109
Connect time:	0.086815
PreXfer time:	0.141417
StartXfer time:	0.185936

Total time:	0.227121

As expected the look up time is higher posed dns cache clear.

Additional information

You can get additional information by reading the manual pages for curl or even use curl to get information about curl –

$ curl cheat.sh/curl
 cheat.sheets:curl 
# curl
# Command-line tool for transferring data with URL syntax

# Process a single GET request, and show its output on stdout.
curl http://path.to.the/file

# Download a file and specify a new filename.
curl http://example.com/file.zip -o new_file.zip

# Download multiple files.
curl -O URLOfFirstFile -O URLOfSecondFile

# Download all sequentially-numbered files (1-24).
curl http://example.com/pic[1-24].jpg

# Download a file and follow redirects.
curl -L http://example.com/file
....

The net-tools set of packages had been deprecated years back, although the commands are still being in use. Tools such as netstat and ifconfig are part of the net-tools. The alternatives can be installed from iproute2 package.

Which Ubuntu package provides a file/command

daniel@hidmo:/tmp$ sudo dpkg -S $(which ss)
iproute2: /bin/ss

daniel@hidmo:/tmp$ sudo dpkg -S $(which netstat)
net-tools: /bin/netstat

daniel@hidmo:/tmp$ sudo dpkg -S $(which ifconfig)
net-tools: /sbin/ifconfig

daniel@hidmo:/tmp$ sudo dpkg -S $(which ip)
iproute2: /sbin/ip

Not all features of netstat can be replace with ss, but ss combined with ip can do the job.

There is lots of similarity between netstat and ss flags or options. Let us see how we can use ss to substitute for one of the most common uses of netstat – viewing TCP connections and their state, including the process name and ID associated with the socket.

Below list is for IPv4 only (-4 ) flag –

daniel@hidmo:/tmp$ sudo netstat -plant4
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      675/systemd-resolve
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      230810/cupsd
tcp        0      0 127.0.0.1:6379          0.0.0.0:*               LISTEN      853/redis-server 12
tcp        0      0 192.168.10.44:51328       74.6.143.25:443       ESTABLISHED 39005/chrome --type 
tcp        0      0 192.168.10.44:56610       74.6.143.25:5228      ESTABLISHED 39005/chrome --type 
tcp        0      0 192.168.10.44:57920       64.233.177.138:443      ESTABLISHED 39005/chrome --type


The equivalent ss comand is below –

daniel@hidmo:/tmp$ sudo ss -pant4
State          Recv-Q         Send-Q                 Local Address:Port                    Peer Address:Port         Process
LISTEN         0              4096                   127.0.0.53%lo:53                           0.0.0.0:*             users:(("systemd-resolve",pid=675,fd=13))
LISTEN         0              5                          127.0.0.1:631                          0.0.0.0:*             users:(("cupsd",pid=230810,fd=7))
LISTEN         0              511                        127.0.0.1:6379                         0.0.0.0:*             users:(("redis-server",pid=853,fd=6))
ESTAB          0              0                        192.168.10.44:51328                  74.6.143.25:443         users:(("chrome",pid=39005,fd=35))
ESTAB          0              0                        192.168.10.44:56610                  74.6.143.25:5228        users:(("chrome",pid=39005,fd=37))
ESTAB          0              0                        192.168.10.44:57920                 64.233.177.138:443         users:(("chrome",pid=39005,fd=32))

ss has very helpful filtering features, for instance we can filter by source or destination IP address or port and tcp states. In below example, we are looking for TCP connections in TIMEWAIT state to a an http or https port and destined to specific IP CIDR block –

daniel@hidmo:/tmp$ sudo ss -o state time-wait '( dport = :http or dport = :https )' dst 162.247.78.0/24
Netid             Recv-Q             Send-Q                           Local Address:Port                            Peer Address:Port              Process                               
tcp               0                  0                                  192.168.10.44:59318                           162.247.78.1:https              timer:(timewait,58sec,0)             
tcp               0                  0                                  192.168.10.44:59312                           162.247.78.1:https              timer:(timewait,58sec,0)             
tcp               0                  0                                  192.168.10.44:59322                           162.247.78.1:https              timer:(timewait,58sec,0)             
tcp               0                  0                                  192.168.10.44:59328                           162.247.78.1:https              timer:(timewait,59sec,0)             
tcp               0                  0                                  192.168.10.44:59304                           162.247.78.1:https              timer:(timewait,58sec,0)             
tcp               0                  0                                  192.168.10.44:59326                           162.247.78.1:https              timer:(timewait,59sec,0)             
tcp               0                  0                                  192.168.10.44:59320                           162.247.78.1:https              timer:(timewait,58sec,0)             
tcp               0                  0                                  192.168.10.44:59306                           162.247.78.1:https              timer:(timewait,58sec,0)             
tcp               0                  0                                  192.168.10.44:59334                           162.247.78.1:https              timer:(timewait,59sec,0)             
tcp               0                  0                                  192.168.10.44:59314                           162.247.78.1:https              timer:(timewait,58sec,0)             
tcp               0                  0                                  192.168.10.44:59308                           162.247.78.1:https              timer:(timewait,58sec,0)    


References –

https://www.redhat.com/sysadmin/ss-command

https://man7.org/linux/man-pages/man8/ss.8.html

https://linux.die.net/man/8/netstat


How to generate a sequence of numbers in Bash scripting

  • Use Bash 4’s brace expansion
$ echo {1..10}
1 2 3 4 5 6 7 8 9 10

$ echo {1..10..2}
1 3 5 7 9
  • Use “seq” command
$ seq 1 10
1
2
3
4
5
6
7
8
9
10

$ seq 1 2 10
1
3
5
7
9

$ echo $(seq 1 10)
1 2 3 4 5 6 7 8 9 10

Get HTTP headers

Linux – view HTTP header response using curl, httpie, GET, nmap


Most users are interested in the content they receive when they visit a web site. There is an extra information web clients and servers exchange – HTTP headers. HTTP headers let the client and the server pass additional information with an HTTP request or response.

So how do we view the HTTP response from a remove web server? There are several tools for these

1. Curl : use ‘-I’ flag

   -I, --head
          (HTTP FTP FILE) Fetch the headers only! HTTP-servers feature the command HEAD which this uses to get nothing but  the  header  of  a
          document. When used on an FTP or FILE file, curl displays the file size and last modification time only.
$ curl -I google.com
HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Date: Sun, 02 Aug 2020 13:48:01 GMT
Expires: Tue, 01 Sep 2020 13:48:01 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN

2. httpie : Use ‘-h h’ flag

--print WHAT, -p WHAT
       String specifying what the output should contain:
      'H' request headers
      'B' request body
      'h' response headers
      'b' response body
$ http www.google.com --print h
 HTTP/1.1 200 OK
 Cache-Control: private, max-age=0
 Content-Encoding: gzip
 Content-Length: 5256
 Content-Type: text/html; charset=ISO-8859-1
 Date: Sun, 02 Aug 2020 13:50:50 GMT
 Expires: -1
 P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
 Server: gws
 Set-Cookie: 1P_JAR=2020-08-02-13; expires=Tue, 01-Sep-2020 13:50:50 GMT; path=/; domain=.google.com; Secure
 Set-Cookie: NID=TRUNCATED; expires=Mon, 01-Feb-2021 13:50:50 GMT; path=/; domain=.google.com; HttpOnly
 X-Frame-Options: SAMEORIGIN
 X-XSS-Protection: 0

3. GET – lwp-request : ‘Ed’ flag

In many Linux distros, GET is an alias for lwp-request. It gives way more detailed information in the response header, including SSL parameters.

  -E  Print response status chain with full response headers.

  -d  Do not print the content of the response.
$ GET linux.com -Ed
GET http://linux.com
User-Agent: lwp-request/6.31 libwww-perl/6.31

301 Moved Permanently
Cache-Control: public, max-age=86400
Connection: close
Date: Sun, 02 Aug 2020 13:56:35 GMT
Via: 1.1 varnish
Accept-Ranges: bytes
Age: 43368
Location: https://linux.com/
Server: nginx
Vary: Cookie, Cookie
Content-Length: 162
Content-Type: text/html
Client-Date: Sun, 02 Aug 2020 13:56:35 GMT
Client-Peer: REDACTED
Client-Response-Num: 1
Title: 301 Moved Permanently
X-Cache: HIT, HIT
X-Cache-Hits: 1, 1
X-Pantheon-Styx-Hostname: styx-fe3-a-745747b57-x7rhq
X-Served-By: cache-mdw17324-MDW, cache-fty21379-FTY
X-Styx-Req-Id: 01697a62-d463-11ea-a64f-aabcb0e0cfdc
X-Timer: S1596376596.936127,VS0,VE1

GET https://linux.com/
User-Agent: lwp-request/6.31 libwww-perl/6.31

301 Moved Permanently
Cache-Control: public, max-age=86400
Connection: close
Date: Sun, 02 Aug 2020 13:56:36 GMT
Via: 1.1 varnish
Accept-Ranges: bytes
Age: 43368
Location: https://www.linux.com/
Server: nginx
Vary: Cookie, Cookie
Content-Length: 0
Content-Type: text/html; charset=UTF-8
Client-Date: Sun, 02 Aug 2020 13:56:36 GMT
Client-Peer: REDACTED
Client-Response-Num: 1
Client-SSL-Cert-Issuer: /C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
Client-SSL-Cert-Subject: /CN=linux.com
Client-SSL-Cipher: ECDHE-RSA-AES128-GCM-SHA256
Client-SSL-Socket-Class: IO::Socket::SSL
Strict-Transport-Security: max-age=300
X-Cache: HIT, HIT
X-Cache-Hits: 1, 1
X-Pantheon-Styx-Hostname: styx-fe3-b-64d9844f89-tc7zl
X-Served-By: cache-mdw17340-MDW, cache-pdk17820-PDK
X-Styx-Req-Id: 01bf3709-d463-11ea-baef-1ede833e594e
X-Timer: S1596376596.065153,VS0,VE1

GET https://www.linux.com/
User-Agent: lwp-request/6.31 libwww-perl/6.31

200 OK
Cache-Control: public, max-age=1800
Connection: close
Date: Sun, 02 Aug 2020 13:56:36 GMT
Via: 1.1 varnish
Accept-Ranges: bytes
Age: 1659
Server: nginx
Vary: Accept-Encoding, Cookie, Cookie
Content-Length: 126289
Content-Type: text/html; charset=UTF-8
Client-Date: Sun, 02 Aug 2020 13:56:36 GMT
Client-Peer: REDACTED
Client-Response-Num: 1
Client-SSL-Cert-Issuer: /C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
Client-SSL-Cert-Subject: /CN=linux.com
Client-SSL-Cipher: ECDHE-RSA-AES128-GCM-SHA256
Client-SSL-Socket-Class: IO::Socket::SSL
Link: &lt;https://www.linux.com/wp-json/>; rel="https://api.w.org/"
Link: &lt;https://www.linux.com/>; rel=shortlink
Strict-Transport-Security: max-age=300
Title: Linux.com - News For Open Source Professionals
X-Cache: HIT, MISS
X-Cache-Hits: 5, 0
X-Meta-Charset: UTF-8
X-Meta-Description: Linux.com is the go-to resource for open source professionals to learn about the latest in Linux and open source technology, careers, best practices, and industry trends. Get news, information, and tutorials to help advance your next project or career – or just to simply stay informed.
X-Meta-Generator: WordPress 5.4.2
X-Meta-Twitter-Card: summary_large_image
X-Meta-Twitter-Description: Linux.com is the go-to resource for open source professionals to learn about the latest in Linux and open source technology, careers, best practices, and industry trends. Get news, information, and tutorials to help advance your next project or career – or just to simply stay informed.
X-Meta-Twitter-Image: https://www.linux.com/wp-content/uploads/2019/08/ldc_social.jpg
X-Meta-Twitter-Title: Linux.com - News For Open Source Professionals
X-Meta-Viewport: width=device-width, initial-scale=1.0
X-Pantheon-Styx-Hostname: styx-fe3-a-745747b57-mfmk7
X-Served-By: cache-mdw17340-MDW, cache-pdk17866-PDK
X-Styx-Req-Id: 1df2da1b-d4c4-11ea-84e9-925461917092
X-Timer: S1596376596.261327,VS0,VE18

Nmap : –script=http-headers flag

Nmap is a network discovery tool but it can be used for scanning http headers as well. The port number has to be specified, otherwise nmap will scan several common ports.

$ nmap --script=http-headers google.com -p 80

Starting Nmap 7.60 ( https://nmap.org ) at 2020-08-02 10:00 PDT
Nmap scan report for google.com (172.217.15.110)
Host is up (0.026s latency).
rDNS record for 172.217.15.110: iad30s21-in-f14.1e100.net

PORT   STATE SERVICE
80/tcp open  http
| http-headers: 
|   Location: http://www.google.com/
|   Content-Type: text/html; charset=UTF-8
|   Date: Sun, 02 Aug 2020 14:00:10 GMT
|   Expires: Tue, 01 Sep 2020 14:00:10 GMT
|   Cache-Control: public, max-age=2592000
|   Server: gws
|   Content-Length: 219
|   X-XSS-Protection: 0
|   X-Frame-Options: SAMEORIGIN
|   Connection: close
|   
|_  (Request type: GET)

Nmap done: 1 IP address (1 host up) scanned in 0.69 seconds



$ nmap --script=http-headers google.com -p 443

Starting Nmap 7.60 ( https://nmap.org ) at 2020-08-02 10:00 PDT
Nmap scan report for google.com (172.217.15.110)
Host is up (0.027s latency).
rDNS record for 172.217.15.110: iad30s21-in-f14.1e100.net

PORT    STATE SERVICE
443/tcp open  https
| http-headers: 
|   Location: https://www.google.com/
|   Content-Type: text/html; charset=UTF-8
|   Date: Sun, 02 Aug 2020 14:00:13 GMT
|   Expires: Tue, 01 Sep 2020 14:00:13 GMT
|   Cache-Control: public, max-age=2592000
|   Server: gws
|   Content-Length: 220
|   X-XSS-Protection: 0
|   X-Frame-Options: SAMEORIGIN
|   Alt-Svc: h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
|   Connection: close
|   
|_  (Request type: GET)

Nmap done: 1 IP address (1 host up) scanned in 0.74 seconds

Terraform – show logging


Enable debugging terraform


By default, a terraform plan or apply run only shows the resources to create, update or delete. It doesn’t give much detail on the activity terraform is doing behind the scene. All the debug logging would especially be helpful when you encounter an error.

Here is a typical terraform plan output, in this case it is a code for creating a firewall rule in Google cloud platform(GCP) –

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
...
------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

# google_compute_firewall.glb-allow will be created
  + resource "google_compute_firewall" "glb-allow" {
      + creation_timestamp = (known after apply)
      + description        = "Allow GLB proxies"
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + id                 = (known after apply)
      + name               = "allow-glb-access"
      + network            = "internal"
      + priority           = 1000
      + project            = "my-gcp-project"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "130.211.0.0/22",
          + "35.191.0.0/16",
        ]
      + target_tags        = [
          + "webservers",
        ]

      + allow {
          + ports    = [
              + "80",
              + "443",
              + "8080",
              + "8443",
            ]
          + protocol = "tcp"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Terraform supports an environment variable – TF_LOG – for detailed logging purposes. You can set the logging to different severity levels which includes – TRACE, DEBUG, INFO, WARN, and ERROR

$ TF_LOG=DEBUG terraform plan
2020/03/20 17:03:39 [WARN] Log levels other than TRACE are currently unreliable, and are supported only for backward compatibility.
  Use TF_LOG=TRACE to see Terraform's internal logs.
  ----
2020/03/20 17:03:39 [INFO] Terraform version: 0.12.20  
2020/03/20 17:03:39 [INFO] Go runtime version: go1.12.13
2020/03/20 17:03:39 [INFO] CLI args: []string{"/usr/local/bin/terraform", "plan"}
2020/03/20 17:03:39 [DEBUG] Attempting to open CLI config file: /home/daniel/.terraformrc
2020/03/20 17:03:39 [DEBUG] File doesn't exist, but doesn't need to. Ignoring.
2020/03/20 17:03:39 [INFO] CLI command args: []string{"plan"}
2020/03/20 17:03:39 [DEBUG] checking for provider in "." 
2020/03/20 17:03:39 [DEBUG] checking for provider in "/usr/local/bin"
2020/03/20 17:03:39 [DEBUG] checking for provider in ".terraform/plugins/linux_amd64"
2020/03/20 17:03:39 [DEBUG] found provider "terraform-provider-google_v3.13.0_x5"
....

Terraform also supports saving logs to a file with the TF_LOG_PATH environment variable.

$ TF_LOG=DEBUG TF_LOG_PATH=/tmp/terraform-debug.log terraform plan
...
$ ls -lh /tmp/terraform-debug.log
-rw-r--r-- 1 daniel daniel 15K Mar 20 17:06 /tmp/terraform-debug.log


References

https://www.terraform.io/docs/internals/debugging.html

Linux – Cheat sheet

Using curl to get help on Linux commands, programming languages and more. The most comprehensive cheat sheet.

If you are looking for a Linux and programming cheat sheet, please check
https://github.com/chubin/cheat.sh

It provides nicely colored help page, with plenty of examples in a CLI. Here are some sample runs I did.

Curl cheat sheet


daniel@hidmo:/tmp$ curl cheat.sh/curl
# Download a single file
curl http://path.to.the/file

# Download a file and specify a new filename
curl http://example.com/file.zip -o new_file.zip

# Download multiple files
curl -O URLOfFirstFile -O URLOfSecondFile

# Download all sequentially numbered files (1-24)
curl http://example.com/pic[1-24].jpg

# Download a file and follow redirects
curl -L http://example.com/file

# Download a file and pass HTTP Authentication
curl -u username:password URL 

# Download a file with a Proxy
curl -x proxysever.server.com:PORT http://addressiwantto.access

# Download a file from FTP
curl -u username:password -O ftp://example.com/pub/file.zip

# Get an FTP directory listing
curl ftp://username:password@example.com

# Resume a previously failed download
curl -C - -o partial_file.zip http://example.com/file.zip

# Fetch only the HTTP headers from a response
curl -I http://example.com

# Fetch your external IP and network info as JSON
curl http://ifconfig.me/all/json

# Limit the rate of a download
curl --limit-rate 1000B -O http://path.to.the/file

# POST to a form
curl -F &quot;name=user&quot; -F &quot;password=test&quot; http://example.com

# POST JSON Data
curl -H &quot;Content-Type: application/json&quot; -X POST -d '{&quot;user&quot;:&quot;bob&quot;,&quot;pass&quot;:&quot;123&quot;}' http://example.com

# POST data from the standard in / share data on sprunge.us
curl -F 'sprunge=&amp;lt;-' sprunge.us

Python lists cheat list

daniel@hidmo:/tmp$ curl cheat.sh/python/list
#  python - Why does += behave unexpectedly on lists?
#  
#  The general answer is that += tries to call the __iadd__ special
#  method, and if that isn't available it tries to use __add__ instead.
#  So the issue is with the difference between these special methods.
#  
#  The __iadd__ special method is for an in-place addition, that is it
#  mutates the object that it acts on. The __add__ special method returns
#  a new object and is also used for the standard + operator.
#  
#  So when the += operator is used on an object which has an __iadd__
#  defined the object is modified in place. Otherwise it will instead try
#  to use the plain __add__ and return a new object.
#  
#  That is why for mutable types like lists += changes the object's
#  value, whereas for immutable types like tuples, strings and integers a
#  new object is returned instead (a += b becomes equivalent to a = a +
#  b).
#  
#  For types that support both __iadd__ and __add__ you therefore have to
#  be careful which one you use. a += b will call __iadd__ and mutate a,
#  whereas a = a + b will create a new object and assign it to a. They
#  are not the same operation!

>>> a1 = a2 = [1, 2]
>>> b1 = b2 = [1, 2]
>>> a1 += [3]          # Uses __iadd__, modifies a1 in-place
>>> b1 = b1 + [3]      # Uses __add__, creates new list, assigns it to b1
>>> a2
[1, 2, 3]              # a1 and a2 are still the same list
>>> b2
[1, 2]                 # whereas only b1 was changed

#  For immutable types (where you don't have an __iadd__) a += b and a =
#  a + b are equivalent. This is what lets you use += on immutable types,
#  which might seem a strange design decision until you consider that
#  otherwise you couldn't use += on immutable types like numbers!
#  
#  [Scott Griffiths] [so/q/2347265] [cc by-sa 3.0]

Golang concurrency cheat sheet

daniel@hidmo:/tmp$ curl cheat.sh/go/concurrency
/*
 * go - When should I use concurrency in golang?
 * 
 * Not an expert in Go (yet) but I'd say:
 * 
 * Whenever it is easiest to do so.
 * 
 * The beauty of the concurrency model in Go is that it is not
 * fundamentally a multi-core architecture with checks and balances where
 * things usually break - it is a multi-threaded paradigm that not only
 * fits well into a multi-core architecture, it also fits well into a
 * distributed system architecture.
 * 
 * You do not have to make special arrangements for multiple goroutines
 * to work together harmoniously - they just do!
 * 
 * Here's an example of a naturally concurrent algorithm - I want to
 * merge multiple channels into one. Once all of the input channels are
 * exhausted I want to close the output channel.
 * 
 * It is just simpler to use concurrency - in fact it doesn't even look
 * like concurrency - it looks almost procedural.
 */

/*
  Multiplex a number of channels into one.
*/
func Mux(channels []chan big.Int) chan big.Int {
    // Count down as each channel closes. When hits zero - close ch.
    var wg sync.WaitGroup
    wg.Add(len(channels))
    // The channel to output to.
    ch := make(chan big.Int, len(channels))

    // Make one go per channel.
    for _, c := range channels {
        go func(c &amp;lt;-chan big.Int) {
            // Pump it.
            for x := range c {
                ch &amp;lt;- x
            }
            // It closed.
            wg.Done()
        }(c)
    }
    // Close the channel when the pumping is finished.
    go func() {
        // Wait for everyone to be done.
        wg.Wait()
        // Close.
        close(ch)
    }()
    return ch
}

/*
 * The only concession I have to make to concurrency here is to use a
 * sync.WaitGroup as a counter for concurrent counting.
 * 
 * Note that this is not purely my own work - I had a great deal of help
 * with this here (https:stackoverflow.com/q/19192377/823393).
 * 
 * [OldCurmudgeon] [so/q/19747950] [cc by-sa 3.0]
 */

Please check
https://github.com/chubin/cheat.sh for more information on installation and using its comprehensive features.