· 8 years ago · Apr 09, 2018, 08:04 PM
1CSC 368 – System Programming Languages bash Script Project
2For this project you will write (and test) a bash script to create an inventory of a computer system.
3We will inventory a Linux machine, just as we did a Windows machine with PowerShell. The information we used before came from the Get-WmiObject call which used Windows Management Instrumentation to gather data. There is a similar structure available on Linux and Unix platforms called dmidecode, which we will use for this project. DO NOT PRINT THIS IN BLACK AND WHITE.
4You will need to turn in only the final bash script you create. The initial sections of the project are designed to introduce you to the tools you will need. For most of the information we are seeking, there are multiple ways to gather it. All code in the handout has been tested.
5These are the classes of objects that we used on the Windows system to get the inventory. We also used the host name and the date and time.
6Win32_ComputerSystem
7Win32_BootConfiguration
8Win32_BIOS
9Win32_OperatingSystem
10Win32_TimeZone
11Win32_LogicalDisk (only DriveType=3)
12Win32_Processor
13Win32_PhysicalMemory
14Win32_Product
15Let’s take a look at the information on the sample output and see how we can get it in bash.
16First, there was the page heading that contained the system name and the date and time of the inventory. To get this in Windows we used:
17 (Get-Item env:\Computername).Value
18A quick look on Google tells us there are a couple of ways to do this in bash. The two most common are the hostname command and the uname command with a -n flag. From the command line we just type the command. To include the result in a script as a variable it would be enclosed in parenthesis, preceded by a ‘$’ symbol. For example:
19thishost=â€$(hostname)â€
20would assign the string returned by hostname to a variable named thishost. Try it.
21To see what options are available, type man hostname to display the Linux manual page (also linked above). To look for all the commands that have hostname in the name or short description, perform a manual page key word search by typing:
22 man -k hostname
23
24This makes hostname a key word, rather than trying to lookup a manual page for something called hostname.
25The date and time is returned by the date command. Try typing the command now. date
26We can produce a header similar to what we had with PowerShell with the following command:
27inventoryheader=â€$(hostname -s) \t $(date)â€
28We can then output this to the screen with an echo -e command (-e makes echo use the formatting
29characters, such as \t for tab and \n, see echo info here): Type the command above, followed by: echo -e $inventoryheader
30The first section in our Windows inventory was the computer system information for Manufacturer, Model, Description, PrimaryOwner, SystemType, and BootUpState, which were the properties we included from the object returned by Get-WmiObject -class Win32_ComputerSystem. Here we are dealing with text and text file entries, but the information is still available, with the exception of the PrimaryOwner, which is not a Linux/Unix operating system concept.
31One way to gather this information is with the DMIdecode command, which looks at the SMBios entries or the pseudo-filesystem entries maintained by the operating system. For example, let’s start by looking at what is returned by dmidecode for the type “system.†Type the following command:
32 dmidecode -t system
33We can get some of the specific information using the -s option for dmidecode. Look at the output when you type the following two commands:
34 dmidecode -s system-manufacturer
35 dmidecode -s system-product-name
36Which return the manufacturer and model name. The description we received from our Get-WmiObject call really didn’t tell us much. The label of “AT/AT COMPATIBLE†indicates a desktop system. We can find this information on the Linux system using information about the chassis from the lshw (list hardware) command. Since we aren’t going to use the hundreds of lines of information that can be returned from lshw, we’ll search for the information we want using the grep command.
37Grep is a regular expression matcher that will return every line that matches the expression given. For simplicity, we will use just the string “chassis†while ignoring the case with the -i flag. Try this by typing:
38
39 lshw | grep -i chassis
40We need to extract the second part of the line returned and then strip out the part before the “=â€. We can do this with the awk command. awk is a pattern scanning and processing language that permits more operations than grep. We will use it to select portions of the lines returned by commands. awk separates the line by white space, unless we tell it to use some symbol. Looking at the output of the above command, we want the third section (the part where the word chassis is) and then we want to split that. We can split is with a second awk command where we tell awk to split using the “=†instead of whitespace. We can use the -F flag to tell the computer the = is our delimiter. Finally, awk uses the position as the indicator for which field to work with. We can extract one or more fields by printing (echoing) it in awk. Here we are only printing to the pipe, or as a filter on the output of our series of piped commands. The result will look like the following:
41 lshw | grep -i chassis | awk ‘{print $3}’ | awk -F= ‘{print $2}’
42Try it. Here is a short description of what each portion of our pipe is doing:
43• get the entire hardware listing using lshw
44• get all lines that contain the string chassis, regardless of the case (upper or lower case letters)
45• using whitespace as a separator, choose the third section of the selected line
46• select the second part using the = as a section separator
47Let’s start putting some of this information in our output so we can check our shell script as we build it. Usingecho-easabove,wealsohavetoprovidesomeinformationaboutwhatwearedisplaying. Forthe system information section we have:
48echo -e "\n\nSystem Information"
49echo -e “---------------------------------------------------------------------“
50echo -e "Manufacturer \t\t\t\t :" $(dmidecode -s system-manufacturer)
51echo -e "Product Name \t\t\t\t :" $(dmidecode -s system-product-name)
52echo -e "System Version \t\t\t\t :" $(dmidecode -s system-version)
53echo -e "System Serial Number \t\t\t :" $(dmidecode -s system-serial-number)
54echo -e "System Type \t\t\t\t :" $(lshw | grep -i chassis | awk '{print $3}' | awk -F= '{print $2}')
55We use the $( <command pipeline>) to enclose our command pipelines and get the value returned by the sequence of commands. We can echo them to the screen, as we do here, or assign the value to variables, or even output some results to files to process. We’ll see an example of this later. Put this in your script and see what the output looks like.
56Now let’s take a look at the next section in the Windows inventory. This section is the boot partition. Windows didn’t have a really great way to do this, but we could get the information with BootConfiguration class and caption property for Get-WmiObject in Windows. We need to look at the types of disk devices we have and how they exist on the system. To do this we need to understand how Windows has changed the boot process in a move to the EFI secure boot environment. Let’s get a bit more information from our Linux system and perhaps something more meaningful. First, let’s see if there is an EFI partition on the system. If there is, it will be mounted on the system at the
57
58/boot/efi directory. The first thing to do is find out if /boot/efi exists, and if it does, determine which disk partition is mounted there.
59How do we check for the existence of a file/directory? We did that in the homework. Use -e and then the pathnameinanifstatement. Itwon’ttellusanythingotherthanexistencebutthatshouldbeenough and then we will see how to get the partition information. Looking at homework 5, we have:
60 if [ -e /boot/efi ]
61 then
62 {
63} fi
64Now, how do we see what partitions are mounted where on our system????
65A Google search or a few years of dealing with Linux/Unix would tell you that display filesystem, df, is the command we need. Try typing df by itself on the command line. You can look at the manual page if you want, with the man df command. As a hint, df -h will give us the disk sizes in human readable format instead of blocks (disks are what are called “block devicesâ€). If we look at the output we see that /boot/efi is a mounted device (if there is an efi boot partition). We just need to get the device designation from the information printed by df. Just as with the hunt for the chassis information, we will usegrepandawktoextracttheinformationweneedfromtheoutputofthedf. Wewillputthe following inside our if statement and enclose it in $( ). Before we put it in our script, type:
66 df | grep /boot/efi | awk '{print $1}'
67Putting it into a script we get:
68echo -e "\n\nBoot Information"
69echo -e “---------------------------------------------------------------------“ if [ -e /boot/efi ]
70then
71{
72echo -e "UEFI boot partition: \t\t\t :" $(df | grep /boot/efi | awk '{print $1}') }
73fi
74Now we can add the additional information to find out the root of our file system (where the operating system can be found). This can be done a couple of different ways, but the easiest seems to be using the findmnt command with “/†as the argument. Try typing findmnt / and you should see a listing for the root partition. We just have to extract the second line and then get the second piece of data off the line. We can get the second line with a grep command looking for dev, which means device. The second part can come from an awk command. Try the following command:
75echo -e "Root partition \t\t\t\t :" $(findmnt / | grep dev | awk '{print $2}')
76
77Compared to other inventory sections, the section on the motherboard is pretty straight forward. We will only use dmidecode to extract the information... We’ll get the manufacturer, product name, version, and serial number all using the -s flag with dmidecode. A quick look at the dmidecode manual page (man dmidecode) shows the motherboard arguments as motherboard-manufacturer, motherboard-product-name, motherboard-version, and motherboard-serial- number. Our commands don’t require anything special to extract the results from the output. Try typing:
78 dmidecode -s baseboard-manufacturer
79 dmidecode -s baseboard-product-name
80 dmidecode -s baseboard-version
81 dmidecode -s baseboard-serial-number
82Our script will incorporate these commands with some labels, and will look like (remember to put them inside the $( )):
83echo -e "\n\nMotherboard Information"
84echo -e “---------------------------------------------------------------------“ echo -e "Manufacturer \t\t\t\t :" $(dmidecode -s baseboard-manufacturer)
85echo -e "Product Name \t\t\t\t :" $(dmidecode -s baseboard-product-name)
86echo -e "Version \t\t\t\t :" $(dmidecode -s baseboard-version)
87echo -e "Serial Number \t\t\t\t :" $(dmidecode -s baseboard-serial-number)
88BIOS information is also simple, all dmidecode commands with the -s flag (see the man page). We only have one field that requires some work to retrieve, the revision number for the BIOS. That is the only fieldthatrequiresagrepandawktocapturetheinformationwearelookingfor. Onceweaddaheader for this section and the appropriate labels for each piece of data, we start of the first three fields with the folloinwg:
89echo -e "\n\nBIOS Information"
90echo -e “---------------------------------------------------------------------“ echo -e "BIOS Vendor \t\t\t\t :" $(dmidecode -s bios-vendor)
91echo -e "BIOS Version \t\t\t\t :" $(dmidecode -s bios-version)
92echo -e "BIOS Release Date \t\t\t :" $(dmidecode -s bios-release-date)
93For the BIOS revision, we need to look at the complete BIOS output and get the line for Revision (with theuppercaseR)andthengrabthepartafterthecolon,“:â€. Tryusinganothergrepandawkpair. Type:
94dmidecode -t bios | grep Revision | awk -F: ‘{print $2}’ Remember to put this inside a $( ) construct in your script to get a string value you need to work
95with. The result is:
96echo -e "BIOS Revision \t\t\t\t :" $(dmidecode -t bios | grep Revision | awk -F: '{print $2}')
97
98The operating system is next, and because it is Linux, we might want to get different information. One item that is different is the idea of a distribution. While Windows is the 800-pound gorilla of OSes, there are hundreds of Linux distributions. Each one differs from the others in what software is installed bydefault,whatthedesktoplookslike,whatkindofwindowingpackageisused,etc. Thereareseveral “families†of distribution, based on the original distribution or packaging mechanism a new distribution is based on. This usually determines characteristics such as file locations (for example, where the root files of the web server are located), package manager, main window managers used, etc.
99The distribution we are working on right now is based on Ubuntu, which is based on Debian. As a result, the Debian package management system is used and we know that some administrative tasks will be different than if we were working on a Red Hat based system. What this means is that we should display the distribution as part of the OS. There is also the possibility that two systems with the same distribution might have different kernel software, as updates are released periodically and one system may have been updated while another may not. We should display the kernel version (all Linux distributions share the same kernel code). Finally, there are the usual concerns about 32-bit and 64-bit operating systems and the languages present on the system.
100One extra thing we will include is how long the system has been running. Windows systems (even servers) used to need rebooting every 48 days or so (the system clock wrapped around and could introduce anomalies in the system behavior), though they also used to crash more frequently than that. Regardless, Linux and Unix systems are designed to run for years without needing a reboot. We’ll check how long it has been since the last system start.
101The distribution information can be retrieved using the lsb_release command (lsb stands for Linux Standard Base). Try typing lsb_release -d and looking at the results... What you’ll find is a one line description of the Linux distribution. For the VMs the release is Linux Mint 18.1 Serena. This is in the second part of the line, if we use the colon to separate values. We can then use awk to grab this second part. We might need to get rid of some extraneous characters (such as the tab character) so we will use a stream editor program called sed. Type the following:
102 lsb_release -d | awk -F: ‘{print $2} | sed -e 's/^[ \t]*//'
103sed -e allows us to edit the pipe, in this case it is a search and replace. We will replace all the whitespace with nothing. Everything between the first and second ‘/’ are the characters to search for while the characters between the second and third ‘/’ are the characters to replace them with... in this case nothing. The regular expression is contained in square brackets and the first square bracket has to be identified as a “meta-character†rather than part of the regular expression. For this explanation I will
104use ‘⌴‘ for a space. The regular expression [⌴\t]* looks for any number of spaces or tabs and will match them. The full regular expression appears in code as you see it above. Our script will look like:
105echo -e "\n\nOperating System Information"
106echo -e “---------------------------------------------------------------------“
107echo -e "\nODistribution Name\t\t\t :" $(lsb_release -d|awk -F: '{print $2}'|sed -e 's/^[ \t]*//g')
108
109For kernel version, we go back to our old friend (from the beginning of this document ;-), uname. The -r flagwillgiveusthekernelversionnumber. Wedon’thavetodoanythingspecial,justacalltothe command enclosed in the $( ) construct will do it. Try typing:
110 echo -e "Kernel Version\t\t\t\t :" $(uname -r)
111We can get pretty fancy with the 32/64 bit architecture display, but for the sake of time, we won’t. First we have to find out how to get the architecture. Once again, uname comes to the rescue. This time with the -mflag(thisisalsoaliasedtoarch). Typethefollowingcommand:
112uname -m
113Thisreturnsx86_64forourarchitecture. Wecouldusethisvalueinaniftoprinteither“32-bitâ€or“64- bit†but there is no need. We’ll just echo output of the command with the appropriate label:
114 echo -e "OS Architecture\t\t\t\t :" $(uname -m)
115Our Windows system also printed the language of the system. We can get this through the locale of the computer, but there may be multiple languages installed. The language is not an intrinsic part of the operating system the way it is in Windows. If we just type locale -k LC_IDENTIFICATION. This looks for the key word LC_IDENTIFICATION in the locale database and returns the value(s) associated with it. If you type this you will find that is lists character set information, date format, contact info, etc. The field we want is language. We’ll need to perform an awk to extract everything after the quotation mark and then we may need to strip off the other quotation mark at the end... looks like a job for sed.
116Build your command one piece at a time, using locale, then locale and grep, then locale, grep and awk, etc.:
117locale -k LC_IDENTIFICATION | grep language | awk -F†‘{print $2}’ | sed -e ‘s/â€//g’
118Should return English for us.... Try it.... Uh oh, what went wrong? Ooohhhhhhhhh, we forgot the double quote is a metacharacter and must be “escaped†to be used in our statement.... how do we escape special characters? Oh, back slash. The following should work better (your mileage and command may differ):
119locale -k LC_IDENTIFICATION | grep language | awk -F\†‘{print $2}’ | sed -e ‘s/\â€//g’
120Don’tforget,youneedtwodoublequotesforawkandforsed(unlessyouusethe“=â€inawk). Ifyour editor gets a little lost because of the quotation marks... don’t worry about it. NOTE: The following is
121considered a single command. The backslash at the end of the line tells bash the command is continued on the next line.
122echo -e "OS Language \t\t\t\t :" $(locale -ck LC_IDENTIFICATION | grep language | \ awk -F\†'{print $2}')
123
124How long has our system been running? This called uptime, and there is a command called... uptime. Here is an example output, but we’ll have to figure out how to get only uptime or accept the entire output.
125 13:12:44 up 6 days, 19:15, 1 user, load average: 0.26 0.14 .0.5
126Note,thefollowingdevelopmentassumeanuptimeoutputliketheabove. Yourinventoryshouldlookatall the pieces (is the word “days†in the output?) and develop an appropriate way to handle the output. This will require a set of conditions to determine how to “parse†the uptime results.
127The first entry is the time of day the command was run followed by the actual uptime in days and hours. We can use the commas as separators and choose the first and second fields for output.
128 uptime | awk -F, ‘{print $1â€,â€$2†hoursâ€}’
129That appears to do what we want, though we could strip out the time too by using the word “up†in a second awk statement, such as:
130uptime | awk -F, ‘{print $1â€,â€$2†hoursâ€}’ | awk -Fâ€up†‘{print “up “$2}’
131We have to put the word “up†back in to our output because it is stripped out by the awk command.
132echo -e "System Uptime \t\t\t:" $(uptime | awk -F, ‘{print $1â€,â€$2†hoursâ€}’ | awk -Fâ€up†‘{print “up “$2}’)
133The last bit of information here is the time zone associated with the computer. The timedatectl program allows you to control the date and time on the computer, but will display the current values if you type it without parameters. The value that we want is labeled “Time zone†so that is what we will look for, and we will print the value associated with it. To get rid of the space between the colon and start of the word America, I will search for the string “: “ instead of just searching for colon. That is, I use -Fâ€: “ for the flag instead of -F:
134timedatectl returns several lines of information, we will grep the line with “Time zone†in it and then use awk to get just the data. We wind up with:
135timedatectl| grep “Time zone†| awk -Fâ€: “ ‘{print $2}’
136After trying this on the command line it can go into our script with a label:
137echo -e "Current Timezone \t\t\t : " $(timedatectl | grep "Time zone" | awk -F": " '{print $2}')
138Now we can move on to another section. Keeping with the pattern from our PowerShell inventory, we will find out about the disk drives next. If you recall from our PowerShell script, we had to specify that we were looking at type 3 devices. What is a type 3? Well, we really don’t care that much, but we do care how Linux does the same thing.
139
140One thing to keep in mind about Linux is the concept of everything being a file. You and I know that a printer, network card, and monitor are not computer files. Linux uses the same concept it uses for files for communicating with these and other devices. It standardizes much of the communications mechanisms and makes for a very robust programming environment. Another thing that Linux does is put much of the system operational information into pseudo file systems such as the /proc and /sys files. To the Linux system, these look like normal files and they contain much of the configuration and state information we use to efficiently manage our computers.
141For all the devices on the computer, there are configuration and status files and directories in the /dev file system. If you look at the listings we produce in the next page, you’ll see some specific devices for our system. For example, all of the disk drives on our VMs are on the same drive, /dev/sda1, /dev/sda2, and /dev/sda5 are the three partitions on our virtual machines. I have posted the output from my home computer and you can see there are three hard drives, two internal and one USB that are attached all the time.
142Let’s take a look at how we might get to some of the data. The paragraph above talks about partitions on a disk. Partitions are a way to define subsections of a disk that can be treated as if they were completely different disks. The command line tool used to do this is named fdisk and can be used to display this informationforalltheattachedphysicaldisks. Wecanusethis,combinedwiththegrepcommand,toget the information about the actual disks. We’ve just got to avoid some of the more unusual kinds of logical devices that look like disks (loop back mounts, logical volume maps, and others). We’ll do this by stripping out any lines that contain the strings “loopâ€, “mapperâ€, and “md†from the output from fdisk. We’ll usethe-lflagtogetalisting. Tryjustthatcommand:
143fdisk -l
144For our VM there aren’t many items listed, so we’ll just try to gather the appropriate lines of output using grep and grep -v (remember -v is a flag that says “all lines that do not match†rather than “all lines that matchâ€).
145Displaying the disk information will involve gathering information from several columns produced by the fdisk command. While not complicated, we will use two lines for this command, using the back slash, ‘\’, as described above. Look at the output from the fdisk -l command above and observe that there is a great deal of information. If we want only the disk drives them we can look for the lines that contain the word “Disk†with an uppercase ‘D’, then we will need to find those lines that list a number of bytes, and remove those lines that contain “loopâ€, “mapperâ€, and “md†as described above.
146 fdisk -l | grep Disk | grep bytes | egrep -v "loop|mapper|mdâ€
147We want to print the device path and the size (fields 2, 3 and 4). We can then strip out the ‘:’ and the ‘,’ to makeitlookalittleneater. Oneoptionistouseawkandsedtoperformtheseoperations,aswedidwith previous commands.
148
149 fdisk -l | grep Disk | grep bytes | grep -Ev "loop|mapper|md†| \
150 awk ‘{print $2 “\t\t†$3 “ “ $4}’ | \
151 sed -e ‘s/[:|,]//g’
152Let’s take a quick look at this.
1531. The grep command -E option allows extended expressions with special characters in our search.
1542. The awk command is pretty unspectacular, it just includes tab characters.
1553. The sed command searches for ‘:’ or ‘,’ and removes them (replaces them with nothing) and
156does this globally (repeats the search for the input because of the g at the end).
157Now we only need to echo this output to the screen.
158echo -e "\n\nDisk Storage Information"
159echo -e “---------------------------------------------------------------------“ echo -e “Physical Disks found: “
160TDISK=$(/sbin/fdisk -l |grep Disk|grep bytes|egrep -v "loop|mapper|md")
161echo -e “$TDISK†| awk ‘{print $2 “\t\t†$3 “ “ $4}’ | sed -e ‘s/[:|,]//g’
162We could print additional information on each of the disks. If you look at the example output, taken from my home desktop system, you will see I printed the disk model, vendor, serial number (if present), firmware version, and device path for the disks found. There are two internally installed SATA drives, which are present on the PCI bus on the system, and a third drive which is an external USB drive. You may include this in your inventory if you like. Note that you probably would include this in a business environment along with information on the current usage of the disk drives present on the system. That information will only be available for “mounted†disks on a Linux system.
163One place that additional information is stored is in the /dev file system. As mentioned previously, everything in Linux is considered a file, even the devices attached to the system. Pseudo-filesystems are created at runtime to store dynamic information on the system. /proc stores process information, /sys stores system information, and /dev stores device information. You can get listings of the files in these directories, but keep in mind that some of this is actually retrieved from the device, rather than being a static file on the Linux system.
164Printing extra information is not a requirement for your project, so the following is provided as an example of some additional information and how you can gather it. You can use the following to print the additional information in the sample output:
165 #---------Printing each disk details depending on version-----------#
166 TDISK=$(/sbin/fdisk -l |grep Disk|grep bytes|egrep -v "loop|mapper|md")
167 LDISK=$(echo "$TDISK"|awk '{print $2}'|sed 's/://g'|sort)
168 echo -e "\n\t\t Details Of Each Hard Drive(s) (local) Found"
169 DISK=$(echo "$LDISK"|awk -F"/" '{print $3}')
170 for L in $(echo "$DISK")
171 do
172 {
173 echo -e "------------------------------------------------------------"
174
175 echo -e "\t Disk :" $L
176 echo -e "------------------------------------------------------------"
177 echo -e "Disk Model \t\t\t :" $(cat /sys/block/$L/device/model )
178 echo -e "Disk Vendor \t\t\t :" $(cat /sys/block/$L/device/vendor )
179 echo -e "Disk Serial Number \t\t :" $(/usr/sbin/smartctl -i $L | \
180 grep "Serial Number"|awk -F: '{print $2}' )
181 echo -e "Drive Firmware Version \t :" $(/usr/sbin/smartctl -i $L | \
182 grep "Firmware Version"|awk -F: '{print $2}' )
183 echo -e "Device Path \t\t\t :" $(ls -l /dev/disk/by-path/ | \
184 grep -w $L|grep -o "pci.*" )
185} done
186We’re down to the last two sections of our inventory, the memory and the installed software. We are back to using dmidecode to gather the memory information. First we’ll save the information to a temporary file to save on typing, then we will extract information from those files and from the /proc filesystem. Let’s print our section header first.
187echo -e "\n\nPhysical Memory Information"
188echo -e “---------------------------------------------------------------------“
189Let’s start by saving the output from dmidecode into a file in the /tmp directory. /tmp is used on all Linux and Unix systems to store temporary information. It is usually erased at system startup, though this behavior can be set in the system configuration files under the /etc directory. /tmp is readable and writable by all users on the system. Let’s call the file mem.out
190 dmidecode --type memory > /tmp/mem.out
191We can use sed to extract all the information on devices, which is the latter part of the file, by looking for the phrase “Memory Device†and printing all the lines after that and redirecting it to another file. We need to add a new flag to sed to prevent having each line echoed as it is looked at (normally sed modifies the stream by repeating all the characters it sees and performing whatever action we tell it to, which in this case is to print out each line after finding the matching expression). This flag is the -n flag. Don’t worry, thisisnotaclassonsed,thissectionisforyourinformationonly. Ifyoulookattheexplanationonthe sed man page or the online documentation, you’ll see that we are providing a two argument command. The first argument is the regular expression followed by a comma, and the second argument is the $, which is an indicator of the last line. The p is a print command, to “print the current patter space,†which is the contents of the file. Our result is:
192 sed -n -e '/Memory Device/,$ p' /tmp/mem.out > /tmp/memd.out
193I know, this is kind of hard to follow, but you’ll have this document to use as a reference for the future.
194Now that we’ve saved some working files, let’s start displaying our information. The first thing we want to know is how much memory we have. dmidecode will tell us the capacity of the motherboard, but it doesn’t tell us how much memory is actually installed. For that information we will look at
195
196/proc/meminfo (a lot of Linux runtime information is maintained in a virtual filesystem known as /proc). As mentioned above, this is treated as a text file and we can look at the contents on the screen by typing:
197 cat /proc/meminfo
198As you can see, this includes a lot of information that is important for managing processes, free memory, how many buffers are in use, how much cache, how big the swap space (virtual memory) is in use, etc. We only need the total memory because dmidecode tells us the capacity. We need to get this information from the line labeled “MemTotal†using grep, extract the actual amount of memory with awk, and then we’ll use this value to provide our output in GB. You will also note that this doesn’t look like the amount of RAM we might expect... Looking at the man page for proc we find that this number excludes the room used by the kernel and some reserved memory for OS functions. If we round this number up we will get our expected value. This last part requires a little math, but we’re up to it. First, we’ll convert KB to GB, then we’ll get the “ceiling†function by truncating, taking only the integer part, and adding one. We’ll use two awk statements with math to get our answer.
199echo -e "Total RAM: “ $(grep MemTotal /proc/meminfo | \
200awk '{print $2/1024/1024}’ | awk -F. ‘{print $1+1 “ GBâ€}')
201Note: we put the awks on a second line to make it look better in the shell script. We use the decimal point as the separator to truncate the value and then add one. Since the output of our pipe is the only value printed we get the total memory in GB.
202Some other information may or may not be available due to the implementation of dmidecode, but we’ll try to print it out anyway. In particular, the error detecting and correcting values are sometimes not returned. We’ll give it a try anyway, looking at the original mem.out file we only have to grep for the string “Error Detecting Method†and print out what is listed after the colon. When we include the label for this information we have:
203echo -e "Error Detecting Method \t\t\t: "$(grep -w "Error Detecting Method" \ /tmp/mem.out | awk -F: '{print $2}')
204Note that we can separate parts of a command on separate lines by including the continuation back slash.
205We perform the same action to get the Error Correction Capabilities, once again separating parts on separate lines. With its label we have:
206echo -e "Error Correcting Capabilities \t\t: "$( \
207grep -w -m1 "Error Correcting Capabilities" /tmp/mem.out | \ awk -F: '{print $2}')
208Once again, we have separated parts of a single command line on separate lines using a slash. Be careful when you do this, as the bash interpreter might not understand some continuations. For example, rather
209
210than putting the first back slash after the $(, try putting it after grep -w -m1 and start the next line with the string “Error Correcting Capabilitiesâ€. You will find that you get an error. It is all due to how the shell handles and parses input.
211We’re in the home stretch now. We’ve only got to extract some memory module information and then print out the installed packages. First let’s find out how many modules we have. dmidecode displays information for all of the memory sockets on the motherboard, whether they have memory installed or not. We can check for how many are installed by looking at the installed size of the modules. If the size is “Not Installed†then that socket doesn’t contain a module. We can count them using another flag that grep gives us. First, we’ll look at the memd.out file which has information on the memory modules and extract the lines that contain the string “Installed Size†and then use the -c and -v flags combined to count the number of lines that do not contain “Not Installed.†It sounds complicated, but is really pretty simple. Remember, we are using the /tmp/memd.out file to gather this information . Since we’ve used grepsomuchbefore,I’lljustputthecommandhere. Lookforthe-vcinsecondgrepcommand.
212echo -e "No. Of Memory Module(s) Found\t\t: "$(grep -w "Installed Size" \ /tmp/mem.out | grep -vc "Not Installed")
213There we go, easy-peasy as my kids sometimes say.
214Now for some truly dazzling uses of grep and awk. If you look at the information from dmidecode, you will see several pieces of information on each memory module. We’re going to grab 11 lines, starting with the “Size†and then delete the two lines containing “Set†and “Tagâ€. We’ll then delete any leading white space and then collect all the labels in one file called /tmp/m1.out. We’ll repeat the process for all the values associated with the labels we just saved, and put the result in /tmp.m2.out. Let’s begin with a label:
215echo -e "\nHardware Specification Of Each Memory Module(s) "
216echo -e “---------------------------------------------------------------------“
217grep -E '[[:blank:]]Size: [0-9]+' /tmp/memd.out -A11 | grep -Ev ‘Set|Tag’ | \ sed -e 's/^\s*//'|awk -F: '{print $1}' > /tmp/m1.out
218grep -E '[[:blank:]]Size: [0-9]+' /tmp/memd.out -A11 | grep -Ev ‘Set|Tag’ | \ awk -F: '{print $2}'|sed -e 's/^\s*//' > /tmp/m2.out
219Let’s look at this briefly. First, we look for the string “Size: “ which may have any white space in front of it and where it is followed by one or more digits: '[[:blank:]]Size: [0-9]+'. This is followed by the file name where we perform the search and an instruction to collect 11 lines starting with the one where “Size: “ was found as context (this is the -A flag, with the number 11 telling how many lines of trailing context to output, -B would be the lines before, with -C specifying a total number of lines including lines before and after the matching line). Next we remove lines matching “Set†or “Tag†another grep. Then we remove whitespace at the start of the line with sed and output the label, the first field, with awk -F and collect the output by redirecting to /tmp/m1.out. To finish up, we perform almost
220
221the same thing with the values, removing the white space with sed after grabbing the value with a grep -F: and saving the result in /tmp/m2.out.
222Nowwewilluseacoupleofotherutilitiestomakeouroutputlookalittlebetter. Wewillusethepr command to prepare the output for printing (in this case to the screen). We will suppress printing a header by using the -t flag, merge multiple files by putting their output side-by-side in columns, using the -m flag, display the results in 50 characters wide display using the -w flag, and separate the columns with a “: “ string(colon and space), using the -S:\ flag. The information for the left column will come from /tmp/m1.out, which contains the labels we grabbed above for memory modules. The second column will come from /tmp/m2.out.
223pr -t -m -w 50 -S:\ /tmp/m1.out /tmp/m2.out
224Finally, we’ll clean up by deleting our temporary files. In this case we’ll use a shortcut that allows us to list all of them in a single remove statement. We’ll do this by specifying the path, /tmp, and then listing the file names inside curly braces, “{ }â€.
225rm -rf /tmp/{mem.out,memd.out,m1.out,m2.out}
226The -r flag specifies to remove recursively, so even directories can be removed. The -f flag says to force rm to not prompt and not produce errors on non-existent files. Of course, the rm command means remove ;-).
227Finally, we are ready for the final part, the coup de gras, the listing of software packages. We need to know what Linux distribution we are using because we need to use the package manager to list the installed packages. Just as in Windows, manually installed programs (those that are not put on the machine via the package manager, like this inventory shell) will not be listed. But we are going to list the number of packages installed before we actually list them. This should help you understand how easy it would be to miss something without automated tools.
228Packaging systems in Linux manage all the software that is installed by making sure all the software that is needed for a program to function properly is installed when that program is installed. The system then keeps a database of all the installed packages and can check periodically to see if there are updates. Because most of the software is community maintained, it is possible that any given package may have a new release at any time. Just as with a Windows program, many of the packages installed contain multiple files.
229We could modify our script to take the release name into account or to hunt for the package manager to determine how to list the package names, but that is more complicated than we want to be. It really won’t add to your experience with bash, so we will just use the package manager for all Debian based distributions, called dpkg (Mint is based on Ubuntu, which is based on Debian).
230The way to list all the files is using the command:
231
232 dpkg-query -l
233Unfortunately, this also lists packages that aren’t installed on the current system. We need to fall back on our old friend, grep, to remove the listings that are not installed. The easiest way to remove those that are not installed is to look for the string “ii†and remove all of those lines that do not contain that string. In the man page for dpkg-query you can see the status of files under the list flag. only files with a status of ii are installed and not in need of any other action. Packages that were installed and were later uninstalled will still be listed with a different status, as will files that don’t complete installation for some reason. Let’s start with a heading and a count of the number of installed packages. To get the count, we’ll usegrepiiandaddthe-cflagtocountthenumberoflines. Thefinalpartofourheadingistheonefor the actual listing of packages.
234The heading for the package listing is the hardest, most aggravating one in the entire project (not really, but it is more trouble than it is worth to line things up for this project). Screen tabs are different than printer tabs, which means something that is lined up on the screen using tabs will not be lined up on the printer using tabs. Keep this in mind and see the sample inventory output for proof of this statement. All the earlier portions of output were lined up so the colons would be straight on all the output on the screen, but when this is saved to a file and looked at in a text editor, the alignment doesn’t always work. The same thing will be true here. We can line up our headers for the screen or the printer, but not both. The variation will be the number of tab characters we put between the headers. We plan to list out the packages, with their version numbers and with the architecture they were made for (amd64 is “Debian†for all 64-bit machines, all is for either 32-bit or 64-bit or even other architectures, while i386 is the default 32-bit machine architecture).
235echo -e
236echo -e
237echo -e
238echo -e
239"\n\nSoftware Inventory: Installed Packages "
240“---------------------------------------------------------------------“
241“\n\nNumber of installed software packages: “ $(dpkg -l | \
242grep -c “iiâ€)
243“\n\nName \t\t\t\t\t\t Version number \t\t\t\t Architecture:
244Almost there. If you look at the dpkg-query output, you will see 5 columns. The first is the status. As stated above, ii indicates an installed package. The second is the package name, the third is the version number, and the fourth is the architecture. The fifth column is a description of the package, which would be nice for people, but is just too much for a listing of 2000+ packages that will need to be checked for version number, and architecture. We’ll leave that off. Just like we stated above, we will be using the name, version, and architecture for each package. We also want to put the output into a table structure and print it.
245That leaves us with two more commands that we haven’t used yet. As you can imagine, the command to sort our information will be... sort. because we want to sort on the name and the name is the first column, we don’t have any extra flags to use for this command, we just need to put sort in our pipe. The last command is one called column. It will look at our output and organize it into columns for us, we only need to specify how many columns wide, with the -c flag, and how to recognize columns. We’ll wimp out by
246
247letting column determine the number of columns for us, using the -t flag (which luckily works quite well for out information). Our result is:
248dpkg-query -l | grep ii | awk '{print $2 " " $3 " " $4}' | sort | column -c 65 -t
249That’s it, we’re done, there is only.... putting it all together. The most developed sections of code are highlighted in purple. Focus on those as your primary examples for this project. Finally, be careful with your typing (note: copy and paste probably won’t work well due to non-printable characters that many PDF creators add) because spaces, quotations, etc. are very important.
250For this project you will turn in a working inventory bash script. You may save your shell output with output redirection. For example: ./inventory.sh > inventory.txt
251but this is not a required part of the turn-in.
252Estimated completion time: 60-120 minutes