One place for hosting & domains

      Tutorial

      Using Octant with Kubernetes – A Tutorial


      Updated by Linode Written by Linode Community

      What is Octant?

      Octant is a web application that talks to your Kubernetes clusters and provides an easy-to-read dashboard for the objects in your clusters. A marquee feature of Octant is its ability to show your objects and their relations in a graph format:

      Octant Resource Viewer Graph Example

      Octant aims to be developer-centric. It is designed to help application developers (who may not be familiar with Kubernetes) better understand how their applications are deployed and troubleshoot issues when they arise.

      In This Guide

      This guide will explore a few ways to use Octant with some example software deployments:

      Note

      Before you Begin

      The examples in this guide have been tested on a cluster running with Linode with Linode’s CCM and CSI plugins installed. If you would like to install these examples as well, a cluster made with k8s-alpha CLI will meet these criteria.

      Caution

      These examples will create billable services. To stop billing for these services after reading the guide, be sure to read the tear-down instructions at the end of each section. If you created a new cluster for this guide, you can remove the cluster’s Nodes from the Linode Cloud Manager.

      If you remove the resources afterward, you will only be billed for the hour(s) that the resources were present on your account. Consult the How Linode Billing Works guide for detailed information about how hourly billing works. Linode’s pricing page lists the rate for each Linode service.

      Installing Octant

      Octant does not run in your cluster. Instead, it connects to your cluster remotely and observes activity within the cluster. It also does not require special privileges to run–Octant will use the same context information that you use for kubectl. This also means that users with restricted access to a cluster can run Octant; Octant will just display the objects that are visible to that user.

      Octant can be run as a local server process on your workstation. To install and run it:

      1. Install from a package available on your operating system:

        • Linux: download a .deb or .rpm from the releases page on GitHub, then install it with dpkg -i or rpm -i. For example:

          wget https://github.com/vmware-tanzu/octant/releases/download/v0.9.1/octant_0.9.1_Linux-64bit.deb
          dpkg -i octant_0.9.1_Linux-64bit.deb
          
        • macOS, with Homebrew:

          brew install octant
          
        • Windows, with Chocolatey:

          choco install octant
          

        Note

        Alternative installation instructions are available in the project’s README on GitHub.
      2. Then, start the server. In your terminal, run:

        octant
        
      3. If it starts successfully, you should see a message similar to:

          
        ...
        
        Dashboard is available at http://127.0.0.1:7777
        
        

        Note

        If it does not start successfully, check that you can connect to your cluster with kubectl. For example, try running:

        kubectl get pods
        
      4. The dashboard may load automatically in your browser, or you can load the dashboard address (e.g. http://127.0.0.1:7777) in your browser if it does not. You should see the Octant dashboard.

      The interfaces that Octant provides are meant to be a complement, and not a replacement, for kubectl. When using Octant, you may find that you sometimes need to return to kubectl to perform certain actions. Still, the Octant dashboard will serve as a helpful overview when inspecting your cluster.

      Note

      The cluster objects visible in the following screenshots were created by installing the Helm chart for the Ghost blogging software. The How to Install Apps on Kubernetes with Helm 2 guide outlines how to install this software. Please note that this guide uses Helm 2 and not Helm 3 to install the software.
      • When first viewing the dashboard, a list of all of your cluster objects will be shown:

        Octant Cluster Overview

        The left navigation will display a hierarchy of the objects that can be viewed, and the right column will display your objects. In the right corner of the top navigation, you can quickly switch between your workstation’s cluster contexts. The top navigation also allows you to change between cluster namespaces, and to filter your objects by label.

      • Consider the Services item in the left navigation. When clicked on, only your Services will appear in a table to the right:

        Octant Services View

      • Important attributes of your Services will be listed in the columns of this table. In particular, the labels are buttons which can be clicked on:

        Octant Services View - Labels Highlighted

        If you click on the release:my-blog button, all Services without this label will be hidden:

        Octant Services View - release:my-blog

        This view shows that the Ghost Helm chart set up two Services: one for the Ghost front-end, and one for its database.

        Note

        Multiple labels can be selected at the same time.

      • This filter will persist across all other views. For example, if you navigate to the Pods view, only Pods with the release:my-blog label will be shown. To clear your filters, click the clear filters link under the filter dropdown in the top navigation:

        Octant Filter Menu - Clear Filters

      Inspecting an Object

      Clicking on an object will show more detail for that object, including the visual graph view for that object’s relationships.

      • For example, the detail view for the Ghost front-end service shows a summary with Configuration, Status, Metadata, Endpoints, and Events panels:

        Octant Service Detail View - Ghost front-end

        • The Configuration panel makes it easy to see which selector the Service uses to identify your Pods. This can also be directly edited (via the Edit link in the panel). Octant’s development roadmap includes adding more direct-editing features like this.

        • The Status panel shows the external IP address for the Service, because it was implemented with type LoadBalancer. If you view this IP in your browser, you will see the Ghost blog.

        • The Endpoints tab shows the Service’s Pods.

        • The Events tab shows the history of events for the Service. This information can be useful when troubleshooting.

        Note

        Other panels will appear for different object types. For example, Pods will show container environment variables, Volume mounts, node resources requests and limits, and taints, among other information.

      • The Resource Viewer tab will reveal the object relationship graph for this Service:

        Octant Service Detail View - Ghost front-end

        This view will display color-coded cells for each object, indicating the object’s status. The presence or absence of objects in this graph can be helpful when troubleshooting. For example, if a Service’s graph does not show connections to a Pod, then the Service may not be configured to use the right Pod selector.

      • The third YAML tab shows the YAML representation for the object.

        Note

        The detail view for Pods will also show a fourth Logs tab,which will follow and display the logs for a Pod in real-time:

        Octant Pod Detail View - Ghost front-end logs

      To delete the objects created by the Helm chart used in this section, run:

      helm delete <release-name>
      

      The Helm release name for can be determined by running:

      helm ls
      

      Troubleshooting with Octant

      To get a better sense of how Octant can assist with troubleshooting software deployments on Kubernetes, consider this scenario:

      Your colleague Nathan is learning Kubernetes, and he’s deploying a “Hello World” Node.js application as a way of testing his knowledge. You offer to install the application on your cluster to follow along with his work.

      Install the Node.js Application

      1. Nathan has uploaded a Docker image for the application to Docker Hub under the name linodedocs/kubernetes_using-octant-with-kubernetes-a-tutorial_hello-world:v1. He also tells you that the Kubernetes manifest for the application is hosted here: release-1.0.yaml. Download the file and apply it to your cluster with kubectl:

        kubectl apply -f release-1.0.yaml
        

        Your terminal should respond with:

          
        service/hello-world-service created
        deployment.apps/hello-world-deployment created
        
        

        Note

        The Dockerfile and other files used to create the Docker Hub image are located here. Inspecting these files is not necessary for the tutorial.
      2. To learn about how the application is structured on your cluster, you view it in Octant. When visiting the Services tab, you find the new hello-world-service object:

        Octant Services - Hello World with External IP Highlighted

      3. The external IP address for the Service will also be shown in the new table entry (highlighted above). Visiting this address in your browser will return the “Hello World” message as expected.

      4. If you click on the hello-world-service Service and then navigate to the Resource Viewer tab, the relationships for it will be shown. If you click on the Pods cell, a right-hand navigation will appear. The panel in this navigation will show three green dots:

        Octant Service Resource Viewer - Hello World 1.0

        The three green dots indicate that three Pods were created that match the selector for the Service. Clicking on each will navigate to that Pod’s detail view.

      Update the Node.js Application

      1. Nathan tells you that he has updated the application to return “Hello Octant” instead of “Hello World”, and he notes that the new Docker Hub image is named linodedocs/kubernetes_using-octant-with-kubernetes-a-tutorial_hello-world:v2. The new Kubernetes manifest is here: release-2.0.yaml. Download the manifest and apply it to your cluster with kubectl:

        kubectl apply -f release-2.0.yaml
        

        Your terminal should respond with:

          
        service/hello-world-service unchanged
        deployment.apps/hello-world-deployment configured
        
        

        Note

        The Dockerfile and other files used to create the Docker Hub image are located here. Inspecting these files is not necessary for the tutorial.
      2. If you visit the external IP for the application in your browser again, it will still display the same “Hello World” message. If you return to the Resource Viewer graph for the hello-world-service Service in Octant, you’ll see that it has been updated:

        Octant Service Resource Viewer - Hello World 2.0

      3. The Deployment has created a new ReplicaSet to run the updated application under. The orange color for the Deployment, ReplicaSet, and Pods indicates an issue with the update that will need further investigating.

        Note

        The Deployment keeps the older ReplicaSet and Pods running in place until the new ReplicaSet is healthy.

      4. To investigate why the new Pod is unhealthy, click on the orange Pod cell in the graph, and then click on the orange dot in the right-hand panel that appears (highlighted above).

      5. The detail view for the Pod will appear. Scroll down to the Events table at the bottom. A Failed to pull image "linodedocs/kubernetes_using-octant-with-kubernetes-a-tutorial_hello-world:v2a" message should appear in the table:

        Octant Pod Events - Hello World 2.0

      6. The name for the Pod’s image has a typo and should be corrected. On your workstation, open the release-2.0.yaml manifest and update line 32 so that it refers to linodedocs/kubernetes_using-octant-with-kubernetes-a-tutorial_hello-world:v2 instead of linodedocs/kubernetes_using-octant-with-kubernetes-a-tutorial_hello-world:v2a. Save the file, then apply the change to your cluster:

        kubectl apply -f release-2.0.yaml
        
      7. If you visit the external IP for the application in your browser again, it will still display the same “Hello World” message. If you return to the Resource Viewer graph for the hello-world-service Service in Octant, you’ll see that it has been updated again:

        Octant Service Resource Viewer - Hello World 2.0 (after manifest update)

      8. The new Pod is no longer colored orange, but the ReplicaSet still is, indicating that other problems also need to be fixed. Click on the orange ReplicaSet cell in the graph, and then click on the title of the right-hand panel that appears (highlighted above).

      9. The detail view for the ReplicaSet will appear. The Pods panel on this page will show that the Pod has restarted several times:

        Octant ReplicaSet Pods - Hello World 2.0

      10. Clicking on the Pod’s name in this panel will take you to the detail view for it. This Events panel in this view will show the recent restarts. A good next step would be to investigate the logs for the Pod, but you may find that the Logs tab shows no content. In this case, it’s worth switching out to kubectl to try and get logs from previous restarts:

        kubectl logs hello-world-deployment-7b69c98754-f4zk5 --previous=true
        

        Note

        Replace the Pod name above with the name of yours.

          
        /usr/src/app/server.js:7
          res.send('Hello Octant);
                   ^^^^^^^^^^^^^^^
        
        SyntaxError: Invalid or unexpected token
            at Module._compile (internal/modules/cjs/loader.js:723:23)
            at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
            at Module.load (internal/modules/cjs/loader.js:653:32)
            at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
            at Function.Module._load (internal/modules/cjs/loader.js:585:3)
            at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
            at startup (internal/bootstrap/node.js:283:19)
            at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)
        
        
      11. Nathan’s code contains the missing quote syntax error reported in these logs, and you let him know what should be fixed. He responds that he’s uploaded a linodedocs/kubernetes_using-octant-with-kubernetes-a-tutorial_hello-world:v3 image to Docker Hub and tells you to apply his updated manifest here: release-3.0.yaml. Download the file and apply it to your cluster with kubectl:

        kubectl apply -f release-3.0.yaml
        

        Note

        The Dockerfile and other files used to create the Docker Hub image are located here. Inspecting these files is not necessary for the tutorial.
      12. If you return to the external IP for the application in your browser, it should now display “Hello Octant”. The cells in Octant’s Resource Viewer graph should all be colored green as well.

      Troubleshooting Example Tear-Down

      To delete the objects created by the troubleshooting example, run:

      kubectl delete -f release-3.0.yaml
      

      Next Steps

      Octant also provides a robust plugin functionality. Plugins help Octant track cluster resources that are not part of Octant’s core codebase, and Octant can show content from a plugin in-line in your dashboard alongside other standard interface elements. Review Octant’s documentation for more information on plugins.

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      Structs in Go – A Tutorial


      Updated by Linode Contributed by Mihalis Tsoukalos

      Introduction

      Go’s array, slice, and map types can be used to group multiple elements, but they cannot hold values of multiple data types. When you need to group different types of variables and create new data types, you can use structs.

      Note

      Go does not have a concept of classes from other object oriented languages. Structs will be used in similar ways as classes, with important differences. For example, there is no class inheritance feature in Go.

      In this guide you will:

      Before You Begin

      To run the examples in this guide, your workstation or server will need to have Go installed, and the go CLI will need to be set in your terminal’s PATH:

      An introductory-level knowledge of Go is assumed by this guide. If you’re just getting started with Go, check out our Learning Go Functions, Loops, and Errors tutorial.

      A Simple Struct

      The various elements of a struct are called the fields of the struct. The following Go program defines and uses a new struct type called Employee, which is composed of an employee’s first name and their employee ID. The program then instantiates this type:

      employee.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func main() {
          var nathan Employee
          fmt.Println(nathan)
          nathan = Employee{FirstName: "Nathan", employeeID: 8124011}
          fmt.Println(nathan)
      
          var heather Employee = Employee{FirstName: "Heather"}
          fmt.Println(heather)
      
          mihalis := Employee{"Mihalis", 1910234}
          fmt.Println(mihalis)
      }

      Note

      Structs, in particular, and Go types, in general, are usually defined outside the main() function in order to have a global scope and be available to the entire Go package, unless you want to clarify that a type is only useful within the current scope and is not expected to be used elsewhere in your code.

      The output of employee.go will be:

      go run employee.go
      
        
      { 0}
      {Nathan 8124011}
      {Heather 0}
      {Mihalis 1910234}
      
      

      The example illustrates some (but not all) of the ways a struct can be created:

      • When the variable nathan is defined, it is not assigned a value. Go will assign the default zero value to any fields that are not given values. For a string, the zero value is the empty string, which is why a blank space appears to the left of the 0 in the first line of the output.

      • One way to create a struct is to use a struct literal, as shown on line 15. When using a struct literal, you supply a comma-delimited list of the field names and the values they should be assigned.

      • When using a struct literal in this way, you do not need to specify all of the fields, as shown on line 18. Because the employeeID for heather was not defined, it takes on the zero value (for an integer, this is 0).

      • Lastly, you can also use a struct literal without listing the fields’ names, as shown on line 21. The values for the fields will be assigned according to the order that the fields are defined in the struct type definition. You must supply values for all of the fields in order to use this syntax.

        Note

        The mihalis variable is defined using the := syntax, which infers the Employee type for the variable from the assigned value.

      Comparing Structs

      Structs can be compared for equality. Two structs are equal if they have the same type and if their fields’ values are equal.

      employee.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func main() {
          employee1 := Employee{"Heather", 1910234}
          employee2 := Employee{"Heather", 1910234}
          fmt.Println(employee1 == employee2)
      }

      The output of employee.go will be:

      go run employee.go
      
        
      true
      
      

      Note

      Structs cannot be ordered with operators like greater-than > or less-than <.

      Accessing Fields

      You can access a specific field using the struct variable name followed by a . character followed by the name of the field (also referred to as dot notation). Given an Employee variable named mihalis, the struct’s two fields can be individually accessed as mihalis.FirstName and mihalis.employeeID:

      employee.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func main() {
          mihalis := Employee{"Mihalis", 1910234}
          fmt.Println("My name is", mihalis.FirstName, "and my employee ID is", mihalis.employeeID)
      }

      The output of employee.go will be:

      go run employee.go
      
        
      My name is Mihalis and my employee ID is 1910234
      
      

      Public and Private Fields

      In order to be able to use a struct and its fields outside of the Go package where the struct type is defined, both the struct name and the desired field names must begin with an uppercase letter. Therefore, if a struct has some field names that begin with a lowercase letter, then these particular fields will be private to the Go package that the struct is defined. This is a global Go rule that also applies to functions and variables.

      To illustrate, consider these two Go files:

      employee/employee.go
      1
      2
      3
      4
      5
      6
      
      package employee
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      main.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      package main
      
      import (
          "fmt"
          . "./employee"
      )
      
      func main() {
          mihalis := Employee{"Mihalis", 1910234}
          fmt.Println("My name is", mihalis.FirstName, "and my employee ID is", mihalis.employeeID)
      }

      Note

      In this example, employee.go is created within an employee directory.

      The output of main.go will be:

      go run main.go
      
        
      # command-line-arguments
      ./main.go:9:31: implicit assignment of unexported field 'employeeID' in employee.Employee literal
      ./main.go:10:80: mihalis.employeeID undefined (cannot refer to unexported field or method employeeID)
      
      

      This error reflects the fact that employeeID has a lowercase name and is not an exported field of the Employee struct.

      Value Semantics

      By default, when a struct is assigned to a variable, it is copied. Consider this example:

      employee.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func main() {
          employee1 := Employee{"Nathan", 8124011}
          fmt.Println("employee1:", employee1)
          employee2 := employee1
          employee2.FirstName = "Andy"
          employee2.employeeID = 1231410
          employee1.FirstName = "Nate"
          fmt.Println("employee1:", employee1)
          fmt.Println("employee2:", employee2)
      }

      The output of employee.go will be:

      go run employee.go
      
        
      employee1: {Nathan 8124011}
      employee1: {Nate 8124011}
      employee2: {Andy 1231410}
      
      

      The employee2 := employee1 assignment creates a copy of employee1 and saves it in employee2. Changing the employee1 variable will not affect the contents of employee2 after the assignment.

      Value Semantics with Functions

      A struct can be passed to a function. By default, the struct will be copied to its function argument variable. Consider this example:

      employee.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func ChangeEmployeeID(e Employee, newID int) {
          e.employeeID = newID
      }
      
      func main() {
          employee1 := Employee{"Nathan", 8124011}
          fmt.Println(employee1)
          ChangeEmployeeID(employee1, 1012843)
          fmt.Println(employee1)
      }

      The output of employee.go will be:

      go run employee.go
      
        
      {Nathan 8124011}
      {Nathan 8124011}
      
      

      Calling the ChangeEmployeeID function has no effect on the value of employee outside of the function scope. As a result, the output of the print statement on line 20 will be the same as the output of line 18’s print statement.

      Pointers and Structs

      As Go supports pointers, you can create pointers to structs. The use of pointer structs is illustrated in pointers.go.

      pointers.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func main() {
          var employeePointer1 *Employee = &Employee{"Nathan", 1201921}
          fmt.Println("Getting a specific struct field:", (*employeePointer1).FirstName)
          fmt.Println("With implicit dereferencing:", employeePointer1.FirstName)
      
          employeePointer2 := employeePointer1
          employeePointer2.FirstName = "Nate"
          fmt.Println("FirstName for employeePointer2:", employeePointer2.FirstName)
          fmt.Println("FirstName for employeePointer1:", employeePointer1.FirstName)
      }

      The output of pointers.go will be:

      go run pointers.go
      
        
      Getting a specific struct field: Nathan
      With implicit dereferencing: Nathan
      FirstName for employeePointer2: Nate
      FirstName for employeePointer1: Nate
      
      

      employeePointer1 points to the memory location of the struct created with the struct literal on line 13. Inserting an ampersand (&) before the struct literal (e.g. Employee{"Nathan", 1201921}) indicates that the memory location for it should be assigned.

      Line 14 shows how to dereference the pointer by inserting a * before the variable name, which tells Go to return the struct located at the memory location of your pointer. Surrounding this with parentheses and then using dot notation (e.g. (*employeePointer1).FirstName) allows you to access fields within the struct.

      However, Go allows you to implicitly dereference a pointer to a struct in this circumstance. This means that you can simply use normal dot notation (e.g. employeePointer1.FirstName) to access fields, even if your struct variable is a pointer.

      Lines 17-20 show that creating a second pointer to a struct allows you to manipulate that struct from another variable. In this case, the value of the FirstName field for employeePointer1 has been updated after it was assigned through employeePointer2 on line 18. This is in contrast with the value semantics demonstrated previously.

      Pointers and Structs and Functions

      Passing a pointer to a struct as an argument to a function will allow you to mutate that struct from inside the function scope. Consider this example:

      pointers.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func ChangeEmployeeID(e *Employee, newID int) {
          e.employeeID = newID
      }
      
      func main() {
          employeePointer1 := &Employee{"Nathan", 8124011}
          fmt.Println(*employeePointer1)
          ChangeEmployeeID(employeePointer1, 1012843)
          fmt.Println(*employeePointer1)
      }

      The output of pointers.go will be:

      go run pointers.go
      
        
      {Nathan 8124011}
      {Nathan 8124011}
      
      

      Alternatively, using this code in the main function instead will produce identical results:

      pointers.go
      1
      2
      3
      4
      5
      6
      
      func main() {
          employee1 := Employee{"Nathan", 8124011}
          fmt.Println(employee1)
          ChangeEmployeeID(&employee1, 1012843)
          fmt.Println(employee1)
      }

      Methods

      Go methods allow you to associate functions with structs. A method definition looks like other function definitions, but it also includes a receiver argument. The receiver argument is the struct that you wish to associate the method with.

      Once defined, the method can be called using dot-notation on your struct variable. Here’s an example of what this looks like:

      method.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func (e Employee) PrintGreeting() {
          fmt.Println("My name is", e.FirstName, "and my employee ID is", e.employeeID)
      }
      
      func main() {
          employee1 := Employee{"Nathan", 8124011}
          employee1.PrintGreeting()
      }

      The output of method.go will be:

      go run method.go
      
        
      My name is Nathan and my employee ID is 8124011
      
      

      The receiver argument is listed in parentheses, prior to the function name, and has the syntax (variableName Type); see line 12 for an example of this.

      Pointers and Methods

      Using a pointer as the receiver type will allow you to mutate the pointed-to struct from within the method’s scope:

      method.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func (e *Employee) ChangeEmployeeID(newID int) {
          e.employeeID = newID
      }
      
      func main() {
          var employeePointer1 *Employee = &Employee{"Nathan", 8124011}
          fmt.Println(*employeePointer1)
          employeePointer1.ChangeEmployeeID(1017193)
          fmt.Println(*employeePointer1)
      }

      The output of method.go will be:

      go run method.go
      
        
      {Nathan 8124011}
      {Nathan 1017193}
      
      

      You can also call a method with a pointer-type receiver on a normal non-pointer struct variable. Go will automatically convert the non-pointer struct variable to its memory location, and the struct will still be mutated within the function scope. This example will produce identical results to the one above:

      method.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func (e *Employee) ChangeEmployeeID(newID int) {
          e.employeeID = newID
      }
      
      func main() {
          var employee1 Employee = Employee{"Nathan", 8124011}
          fmt.Println(employee1)
          employee1.ChangeEmployeeID(1017193)
          fmt.Println(employee1)
      }

      Creating Structs

      In addition to the struct literal syntax used so far, there are a few other common ways to create a struct:

      Constructor Functions

      One common pattern for creating structs is with a “constructor” function. In Go, this is just a normal function that returns a struct, or a pointer to a struct. This example will demonstrate returning a pointer to a struct:

      constructor.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      
      package main
      
      import (
          "fmt"
      )
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      
      func NewEmployee(name string, employeeID int) *Employee {
          if employeeID <= 0 {
              return nil
          }
          return &Employee{name, employeeID}
      }
      
      func main() {
          employeePointer1 := NewEmployee("Nathan", 8124011)
          fmt.Println(*employeePointer1)
      }

      This approach for creating new struct variables allows you to check whether the provided information is correct and valid in advance; for example, the above code checks the passed employeeID from lines 13 to 15. Additionally, with this approach you have a central point where struct fields are initialized, so if there is something wrong with your fields, you know exactly where to look.

      Note

      For those of you with a C or C++ background, it is perfectly legal for a Go function to return the memory address of a local variable. Nothing gets lost, so everybody is happy!

      Using the new Keyword

      Go supports the new keyword that allows you to allocate new objects with the following syntax:

      1
      
      variable := new(StructType)

      new has these behaviors:

      The following code example explores this behavior in more depth:

      new.go
       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
      
      package main
      
      import (
          "encoding/json"
          "fmt"
      )
      
      func prettyPrint(s interface{}) {
          p, _ := json.MarshalIndent(s, "", "t")
          fmt.Println(string(p))
      }
      
      type Contact struct {
          Name string
          Main Telephone
          Tel  []Telephone
      }
      
      type Telephone struct {
          Mobile bool
          Number string
      }
      
      func main() {
          contact := new(Contact)
          telephone := new(Telephone)
      
          if contact.Main == (Telephone{}) {
              fmt.Println("contact.Main is an empty Telephone struct.")
          }
          fmt.Println("contact.Main")
          prettyPrint(contact.Main)
      
          if contact.Tel == nil {
              fmt.Println("contact.Tel is nil.")
          }
      
          fmt.Println("contact")
          prettyPrint(contact)
          fmt.Println("telephone")
          prettyPrint(telephone)
      }

      Note

      The prettyPrint() function is just used for printing the contents of a struct in a readable and pleasant way with the help of the json.MarshalIndent() function.

      Executing new.go will generate the following output:

      go run new.go
      
        
      contact.Main is an empty Telephone struct.
      contact.Main
      {
          "Mobile": false,
          "Number": ""
      }
      contact.Tel is nil.
      contact
      {
          "Name": "",
          "Main": {
              "Mobile": false,
              "Number": ""
          },
          "Tel": null
      }
      telephone
      {
          "Mobile": false,
          "Number": ""
      }
      
      
      • As Record.Tel is a slice, its zero value is nil. Lines 34-36 show that comparing it to nil returns true.
      • Record.Main is a Telephone struct, so it cannot be compared to nil – it can only be compared to Telephone{}, as demonstrated in lines 28-30.

      Structs and JSON

      Structs are really handy when we have to work with JSON data. This section is going to present a simple example where a struct is used for reading a text file that contains data in the JSON format and for creating data in the JSON format.

      json.go
       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
      
      package main
      
      import (
          "encoding/json"
          "fmt"
          "os"
      )
      
      type Record struct {
          Name    string
          Surname string
          Tel     []Telephone
      }
      
      type Telephone struct {
          Mobile bool
          Number string
      }
      
      func loadFromJSON(filename string, key interface{}) error {
          in, err := os.Open(filename)
          if err != nil {
              return err
          }
      
          decodeJSON := json.NewDecoder(in)
          err = decodeJSON.Decode(key)
          if err != nil {
              return err
          }
          in.Close()
          return nil
      }
      
      func saveToJSON(filename *os.File, key interface{}) {
          encodeJSON := json.NewEncoder(filename)
          err := encodeJSON.Encode(key)
          if err != nil {
              fmt.Println(err)
              return
          }
      }
      
      func main() {
          arguments := os.Args
          if len(arguments) == 1 {
              fmt.Println("Please provide a filename!")
              return
          }
      
          filename := arguments[1]
      
          var myRecord Record
          err := loadFromJSON(filename, &myRecord)
          fmt.Println("JSON file loaded into struct"🙂
          if err == nil {
              fmt.Println(myRecord)
          } else {
              fmt.Println(err)
          }
      
          myRecord = Record{
              Name:    "Mihalis",
              Surname: "Tsoukalos",
              Tel: []Telephone{Telephone{Mobile: true, Number: "1234-5678"},
                  Telephone{Mobile: true, Number: "6789-abcd"},
                  Telephone{Mobile: false, Number: "FAVA-5678"},
              },
          }
      
          fmt.Println("struct saved to JSON"🙂
          saveToJSON(os.Stdout, myRecord)
      }
      • The loadFromJSON() function is used for decoding the data of a JSON file according to a data structure that is given as the second argument to it.

      • The saveToJSON() function creates a JSON encoder variable named encodeJSON, which is associated with a filename, which is where the data is going to be put.

        • The call to Encode() is what puts the data into the desired file after encoding it.
        • In this example, saveToJSON() is called using os.Stdout, which means that data is going to standard output.
        • Last, the myRecord variable contains sample data using the Record and Telephone structs defined at the beginning of the program. It is the contents of the myRecord variable that are processed by saveToJSON().

      Run the JSON Example

      For the purposes of this section we are going to use a simple JSON file named record.json that has the following contents:

      record.json
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      {
          "Name":"Mihalis",
          "Surname":"Tsoukalos",
          "Tel":[
              {"Mobile":true,"Number":"1234-567"},
              {"Mobile":true,"Number":"1234-abcd"},
              {"Mobile":false,"Number":"abcc-567"}
          ]
      }

      Executing json.go and processing the data found in record.json will generate the following output:

      go run json.go record.json
      
        
      {Mihalis Tsoukalos [{true 1234-567} {true 1234-abcd} {false abcc-567}]}
      {"Name":"Mihalis","Surname":"Tsoukalos","Tel":[{"Mobile":true,"Number":"1234-5678"},{"Mobile":true,"Number":"6789-abcd"},{"Mobile":false,"Number":"FAVA-5678"}]}
      
      

      Next Steps

      Structs are a versatile Go data type because they allow you to create new types by combining existing data types. If you confident in the topics covered in this tutorial, try exploring our other guides on the Go language.

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      Solving Real World Problems With Bash Scripts – A Tutorial


      Updated by Linode Contributed by Mihalis Tsoukalos

      Introduction

      This guide presents some of the advanced capabilities of the bash shell by showing practical and fully functional bash scripts. It also illustrates how you can work with dates and times in bash scripts and how to write and use functions in bash.

      In This Guide

      In this guide, you will find the following information about bash scripts:

      Note

      This guide is written for a non-root user. Depending on your configuration, some commands might require the help of sudo in order to properly execute. If you are not familiar with the sudo command, see the Users and Groups guide.

      Functions in bash shell

      The bash scripting language has support for functions. The parameters of a function can be accessed as $1, $2, etc. and you can have as many parameters as you want. If you are interested in finding out the name of the function, you can use the FUNCNAME variable. Functions are illustrated in functions.sh, which is as follows:

      functions.sh
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      
      #!/bin/bash
      
      function f1 {
          echo Hello from $FUNCNAME!
          VAR="123"
      }
      
      f2() {
          p1=$1
          p2=$2
          sum=$((${p1} + ${p2}))
          echo "${sum}"
      }
      
      f1
      echo ${VAR}
      
      mySum="$(f2 1 2)"
      echo mySum = $mySum
      
      mySum="$(f2 10 -2)"
      echo mySum = $mySum

      Run the script with the following command:

      ./functions.sh
      

      The output will look like this:

        
      Hello from f1!
      123
      mySum = 3
      mySum = 8
      
      

      Note

      If you want to check whether a function parameter exists or not, you can use the statement:

      if [ -z "$1" ]
      

      Using bash Functions as Shell Commands

      This is a trick that allows you to use bash functions as shell commands. You can execute the above code as

      . ./functions.sh
      

      Notice the dot in front of the text file. After that you can use f1 as a regular command in the terminal where you executed . ./my_function.sh. You will also be able to use the f2 command with two integers of your choice to quickly calculate a sum. If you want that function to be globally available, you can put its implementation to a bash configuration file that is automatically executed by bash each time a new bash session begins. A good place to put that function implementation would be ~/.bash_profile.

      Working with Dates and Times

      Bash allows you to work with dates and times using traditional UNIX utilities such as date(1). The main difficulty many programmers run into when working with dates and times is getting or using the correct format. This is a matter of using date(1) with the correct parameters and has nothing to do with bash scripting per se. Using date(1) as date +[something] means that we want to use a custom format – this is signified by the use of + in the command line argument of date(1).

      A good way to create unique filenames is to use UNIX epoch time or, if you want your filename to be more descriptive, a date-time combination. The unique nature of the filename is derived from a focus on a higher level of detail in defining your output. If done correctly, you will never have the exact same time value even if you execute the script multiple times on the same UNIX machine.

      The example that follows will shed some light on the use of date(1).

      Using Dates and Times in bash scripts

      The code of dateTime.sh is the following:

      dateTime.sh
       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
      
      #!/bin/bash
      
      # Print default output
      echo `date`
      
      # Print current date without the time
      echo `date +"%m-%d-%y"`
      
      # Use 4 digits for year
      echo `date +"%m-%d-%Y"`
      
      # Display time only
      echo `date +"%T"`
      
      # Display 12 hour time
      echo `date +"%r"`
      
      # Time without seconds
      echo `date +"%H:%M"`
      
      # Print full date
      echo `date +"%A %d %b %Y %H:%M:%S"`
      
      # Nanoseconds
      echo Nanoseconds: `date +"%s-%N"`
      
      # Different timezone by name
      echo Timezone: `TZ=":US/Eastern" date +"%T"`
      echo Timezone: `TZ=":Europe/UK" date +"%T"`
      
      # Print epoch time - convenient for filenames
      echo `date +"%s"`
      
      # Print week number
      echo Week number: `date +"%V"`
      
      # Create unique filename
      f=`date +"%s"`
      touch $f
      ls -l $f
      rm $f
      
      # Add epoch time to existing file
      f="/tmp/test"
      touch $f
      mv $f $f.`date +"%s"`
      ls -l "$f".*
      rm "$f".*

      If you want an even more unique filename, you can also use nanoseconds when defining the behaviour of your script.

      Run the dateTime script:

      ./dateTime.sh
      

      The output of dateTime.sh will resemble the following:

        
      Fri Aug 30 13:05:09 EST 2019
      08-30-19
      08-30-2019
      13:05:09
      01:05:09 PM
      13:05
      Friday 30 Aug 2019 13:05:09
      Nanoseconds: 1567159562-373152585
      Timezone: 06:05:09
      Timezone: 10:05:09
      1567159509
      Week number: 35
      -rw-r--r--  1 mtsouk  staff  0 Aug 30 13:05 1567159509
      -rw-r--r--  1 mtsouk  wheel  0 Aug 30 13:05 /tmp/test.1567159509
      
      

      Bash scripts for Administrators

      This section will present some bash scripts that are generally helpful for UNIX system administrators and power users.

      Watching Free Disk Space

      The bash script that follows watches the free space of your hard disks and warns you when that free space drops below a given threshold – the value of the threshold is given by the user as a command line argument. Notice that if the program gets no command line argument, a default value is used as the threshold.

      freeDisk.sh
       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
      
      #!/bin/bash
      
      # default value to use if none specified
      PERCENT=30
      
      # test for command line arguement is present
      if [[ $# -le 0 ]]
      then
          printf "Using default value for threshold!n"
      # test if argument is an integer
      # if it is, use that as percent, if not use default
      else
          if [[ $1 =~ ^-?[0-9]+([0-9]+)?$ ]]
          then
              PERCENT=$1
          fi
      fi
      
      let "PERCENT += 0"
      printf "Threshold = %dn" $PERCENT
      
      df -Ph | grep -vE '^Filesystem|tmpfs|cdrom' | awk '{ print $5,$1 }' | while read data;
      do
          used=$(echo $data | awk '{print $1}' | sed s/%//g)
          p=$(echo $data | awk '{print $2}')
          if [ $used -ge $PERCENT ]
          then
              echo "WARNING: The partition "$p" has used $used% of total available space - Date: $(date)"
          fi
      done
      • The sed s/%//g command is used for omitting the percent sign from the output of df -Ph.
      • df is the command to report file system disk space usage, while the options -Ph specify POSIX output and human-readable, meaning, print sizes in powers of 1024.
      • awk(1) is used for extracting the desired fields from output of the df(1) command.

      Run ./freeDisk.sh with this command:

      ./freeDisk.sh
      

      The output of freeDisk.sh will resemble the following:

        
      Using default value for threshold!
      Threshold = 30
      WARNING: The partition "/dev/root" has used 61% of total available space - Date: Wed Aug 28 21:14:51 EEST 2019
      
      

      Note

      This script and others like it can be easily executed as cron jobs and automate tasks the UNIX way.

      Notice that the code of freeDisk.sh looks relatively complex. This is because bash is not good at the conversion between strings and numeric values – more than half of the code is for initializing the PERCENT variable correctly.

      Rotating Log Files

      The presented bash script will help you to rotate a log file after exceeding a defined file size. If the log file is connected to a server process, you might need to stop the process before the rotation and start it again after the log rotation is complete – this is not the case with rotate.sh.

      rotate.sh
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      #!/bin/bash
      
      f="/home/mtsouk/connections.data"
      
      if [ ! -f $f ]
      then
        echo $f does not exist!
        exit
      fi
      
      touch ${f}
      MAXSIZE=$((4096*1024))
      
      size=`du -b ${f} | tr -s 't' ' ' | cut -d' ' -f1`
      if [ ${size} -gt ${MAXSIZE} ]
      then
          echo Rotating!
          timestamp=`date +%s`
          mv ${f} ${f}.$timestamp
          touch ${f}
      fi
      • Note that the path to the log file /home/mtsouk/connections.data will not exist by default. You’ll need to either use a log file that already exists like kern.log on some Linux systems, or replace it with a new one.

      • Additionally, the value of MAXSIZE can be a value of your choice, and the script can be edited to suit the needs of your own configuration – you can even make changes to the existing code and provide the MAXSIZE value as a command line argument to the program.

      • The du command is used to estimate the file space usage. It’s use to track the files and directories that are consuming excessive space on the hard disk. The -b option tells this command to print the size in bytes.

      Run the rotate script with the following command:

      ./rotate.sh
      

      The output of rotate.sh when it has reached the threshold defined by MAXSIZE will resemble the following:

        
      Rotating!
      
      

      After running, two files will be created on the system. You can see them with this command:

      ls -l connections.data*
      
        
      -rw-r--r-- 1 mtsouk mtsouk       0 Aug 28 20:18 connections.data
      -rw-r--r-- 1 mtsouk mtsouk 2118655 Aug 28 20:18 connections.data.1567012710
      
      

      If you want to make rotate.sh more generic, you can provide the name of the log file as a command line argument to the bash script.

      Monitoring the Number of TCP Connections

      The presented bash script calculates the number of TCP connections on the current machine and prints that on the screen along with date and time related information.

      tcpConnect.sh
      1
      2
      3
      4
      5
      6
      
      #!/bin/bash
      
      C=$(/bin/netstat -nt | tail -n +3 | grep ESTABLISHED | wc -l)
      D=$(date +"%m %d")
      T=$(date +"%H %M")
      printf "%s %s %sn" "$C" "$D" "$T"
      • The main reason for using the full path of netstat(1) when calling it is to make the script as secure as possible.
      • If you do not provide the full path then the script will search all the directories of the PATH variable to find that executable file.
      • Apart from the number of established connections (defined by the C variable), the script prints the month, day of the month, hour of the day, and minutes of the hour. If you want, you can also print the year and seconds.

      Execute the tcpConnect script with the following command:

      ./tcpConnect.sh
      

      The output will be similar to the following:

        
      8 08 28 16 22
      
      

      tcpConnect.sh can be easily executed as a cron(8) by adding the following to your cron file:

      */4 * * * * /home/mtsouk/bin/tcpConnect.sh >> ~/connections.data
      

      The previous cron(8) job executes tcpConnect.sh every 4 minutes, every hour of each day and appends the results to ~/connections.data in order to be able to watch or visualize them at any time.

      Additional Examples

      Sorting in bash

      The presented example will show how you can sort integer values in bash using the sort(1) utility:

      sort.sh
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      
      #!/bin/bash
      
      # test that at least one argument was passed
      if [[ $# -le 0 ]]
      then
          printf "Not enough arguments!n"
          exit
      fi
      
      count=1
      
      for arg in "[email protected]"
      do
          if [[ $arg =~ ^-?[0-9]+([0-9]+)?$ ]]
          then
              n[$count]=${arg}
              let "count += 1"
          else
              echo "$arg is not a valid integer!"
          fi
      done
      
      sort -n <(printf "%sn" "${n[@]}")
      • The presented technique uses an array to store all integer values before sorting them.
      • All numeric values are given as command line arguments to the script.
      • The script tests whether each command line argument is a valid integer before adding it to the n array.
      • The sorting part is done using sort -n, which sorts the array numerically. If you want to deal with strings, then you should omit the -n option.
      • The printf command, after sort -n, prints every element of the array in a separate line whereas the < character tells sort -n to use the output of printf as input.

      Run the sort script with the following command:

      ./sort.sh 100 a 1.1 1 2 3 -1
      

      The output of sort.sh will resemble the following:

        
      a is not a valid integer!
      1.1 is not a valid integer!
      -1
      1
      2
      3
      100
      
      

      A Game Written in bash

      This section will present a simple guessing game written in bash(1). The logic of the game is based on a random number generator that produces random numbers between 1 and 20 and expects from the user to guess them.

      guess.sh
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      
      #!/bin/bash
      NUMGUESS=0
      
      echo "$0 - Guess a number between 1 and 20"
      
      (( secret = RANDOM % 20 + 1 ))
      
      while [[ guess -ne secret ]]
      do
          (( NUMGUESS = NUMGUESS + 1 ))
          read -p "Enter guess: " guess
      
          if (( guess < $secret )); then
              echo "Try higher..."
          elif (( $guess > $secret )); then
              echo "Try lower..."
          fi
      done
      
      printf "Yes! You guessed it in $NUMGUESS guesses.n"

      Run the guess script:

      ./guess.sh
      

      The output of guess.sh will resemble the following:

        
      ./guess.sh - Guess a number between 1 and 20
      Enter guess: 1
      Try higher...
      Enter guess: 5
      Try higher...
      Enter guess: 7
      Try lower...
      Enter guess: 6
      Yes! You guessed it in 4 guesses.
      
      

      Calculating Letter Frequencies

      The following bash script will calculate the number of times each letter appears on a file.

      freqL.sh
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      #!/bin/bash
      
      if [ -z "$1" ]; then
          echo "Usage: $0 filename."
          exit 1
      fi
      
      filename=$1
      
      while read -n 1 c
      do
          echo "$c"
      done < "$filename" | grep '[[:alpha:]]' | sort | uniq -c | sort -nr
      • The script reads the input file character by character, prints each character, and processes the output using the grep, sort, and uniq commands to count the frequency of each character.
      • The [:alpha:] pattern used by grep(1) matches all alphabetic characters and is equivalent to A-Za-z.
      • If you also want to include numeric characters in the output, you should use [:alnum:] instead.
      • Additionally, if you want the output to be sorted alphabetically instead of numerically, you can execute freqL.sh and then process its output using the sort -k2,2 command.

      Run the freqL script:

      ./freqL.sh text.txt
      

      The output of freqL.sh will resemble the following:

        
         2 b
         1 s
         1 n
         1 i
         1 h
         1 a
      
      

      Note

      The file text.txt will not exist by default. You can use a pre-existing text file to test this script, or you can create the text.txt file using a text editor of your choice.

      Timing Out read Operations

      The read builtin command supports the -t timeout option that allows you to time out a read operation after a given time, which can be very convenient when you are expecting user input that takes too long. The technique is illustrated in timeOut.sh.

      timeOut.sh
       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
      
      #!/bin/bash
      
      if [[ $# -le 0 ]]
      then
          printf "Not enough arguments!n"
          exit
      fi
      
      TIMEOUT=$1
      VARIABLE=0
      
      while :
      do
        ((VARIABLE = VARIABLE + 1))
        read -t $TIMEOUT -p "Do you want to Quit(Y/N): "
        if [ $VARIABLE -gt $TIMEOUT ]; then
          echo "Timing out - user response took too long!"
          break
        fi
      
        case $REPLY in
        [yY]*)
          echo "Quitting!"
          break
          ;;
        [nN]*)
          echo "Do not quit!"
          ;;
        *) echo "Please choose Y or N!"
           ;;
        esac
      done
      • The timeout of the read operation is given as a command line argument to the script, an integer representing the number of seconds that will pass before the script will “time out” and exit.
      • The case block is what handles the available options.
      • Notice that what you are going to do in each case is up to you – the presented code uses simple commands to illustrate the technique.

      Run the timeOut script:

      ./timeOut.sh 10
      

      The output of timeOut.sh will resemble the following:

        
      Do you want to Quit(Y/N): Please choose Y or N!
      Do you want to Quit(Y/N): Y
      Quitting!
      
      

      Alternatively, you can wait the full ten seconds for your script to time out:

        
      Do you want to Quit(Y/N):
      Timing out - user response took too long!
      
      

      Converting tabs to spaces

      The presented utility, which is named t2s.sh, will read a text file and convert each tab to the specified number of space characters. Notice that the presented script replaces each tab character with 4 spaces but you can change that value in the code or even get it as command line argument.

      tabs2spaces.sh
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      #!/bin/bash
      
      for f in "[email protected]"
      do
          if [ ! -f $f ]
          then
            echo $f does not exist!
            continue
          fi
          echo "Converting $f.";
          newFile=$(expand -t 4 "$f");
          echo "$newFile" > "$f";
      done
      • The script uses the expand(1) utility that does the job of converting tabs to spaces for us.
      • expand(1) writes its results to standard output – the script saves that output and replaces the current file with the new output, which means that the original file will change.
      • Although tabs2spaces.sh does not use any fancy techniques or code, it does the job pretty well.

      Run the tabs2spaces script:

      ./tabs2spaces.sh textfile.txt
      

      The output of tabs2spaces.sh will resemble the following:

        
      Converting textfile.txt.
      
      

      Note

      The file textfile.txt will not exist by default. You can use a pre-existing text file to test this script, or you can create the textfile.txt file using a text editor of your choice.

      Counting files

      The following script will look into a predefined list of directories and count the number of files that exist in each directory and its subdirectories. If that number is above a threshold, then the script will generate a warning message.

      ./countFiles.sh
       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
      
      #!/bin/bash
      
      DIRECTORIES="/bin:/home/mtsouk/code:/srv/www/www.mtsoukalos.eu/logs:/notThere"
      
      # Count the number of arguments passed in
      if [[ $# -le 0 ]]
      then
          echo "Using default value for COUNT!"
      else
          if [[ $1 =~ ^-?[0-9]+([0-9]+)?$ ]]
          then
              COUNT=$1
          fi
      fi
      
      while read -d ':' dir; do
          if [ ! -d "$dir" ]
          then
              echo "**" Skipping $dir
              continue
          fi
          files=`find $dir -type f | wc -l`
          if [ $files -lt $COUNT ]
          then
              echo "Everything is fine in $dir: $files"
          else
              echo "WARNING: Large number of files in $dir: $files!"
          fi
      done <<< "$DIRECTORIES:"

      The counting of the files is done with the find $dir -type f | wc -l command. You can read more about the find command in our guide.

      Run the countFiles script:

      ./countFiles.sh 100
      

      The output of countFiles.sh will resemble the following:

        
      WARNING: Large number of files in /bin: 118!
      Everything is fine in /home/mtsouk/code: 81
      WARNING: Large number of files in /srv/www/www.mtsoukalos.eu/logs: 106!
      ** Skipping /notThere
      
      

      Summary

      The bash scripting language is a powerful programming language that can save you time and energy when applied effectively. If you have a lot of useful bash scripts, then you can automate things by creating cron jobs that execute your bash scripts. It is up to the developer to decide whether they prefer to use bash or a different scripting language such as perl, ruby, or python.

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link