1 00:00:02,280 --> 00:00:06,000 In this unit we will see how we can combine commands together, 2 00:00:06,000 --> 00:00:09,560 in order to control the flow of these commands. 3 00:00:09,560 --> 00:00:10,820 Specifically, 4 00:00:10,820 --> 00:00:13,580 we'll see how we can execute commands conditionally 5 00:00:13,580 --> 00:00:15,540 with an if or an else, 6 00:00:15,540 --> 00:00:19,400 and also how can create loops to iterate over files, 7 00:00:19,400 --> 00:00:22,720 or while a condition is true. 8 00:00:22,720 --> 00:00:29,300 The "for" command allows us to iterate over a set of values. 9 00:00:29,300 --> 00:00:33,260 In order to see the time in all three European cities, of London, 10 00:00:33,260 --> 00:00:37,240 Paris and Madrid, I place them in a for loop. 11 00:00:37,240 --> 00:00:40,500 The syntax starts with the iteration statement: 12 00:00:40,500 --> 00:00:44,300 for i in all these three timezones; do, 13 00:00:44,300 --> 00:00:46,880 continues with the commands we specify, 14 00:00:46,880 --> 00:00:52,600 which in our case is to: showtime the America/Los_Angeles time 11:45 15 00:00:52,600 --> 00:00:56,960 in the $i timezone, and concludes with a done. 16 00:00:56,960 --> 00:01:02,920 The i variable receives the values of London, then Paris, and lastly Madrid. 17 00:01:02,920 --> 00:01:07,500 I'm iterating with i through these values and according to the output: 18 00:01:07,500 --> 00:01:13,140 when the time in L.A. is 11:45, in London it is 7:45 PM, 19 00:01:13,140 --> 00:01:17,660 whereas in Paris and Madrid it is 8:45PM. 20 00:01:17,660 --> 00:01:22,240 Apart from simple for loops, we can also define nested for loops. 21 00:01:22,240 --> 00:01:25,120 For instance, to extend the previous example, 22 00:01:25,120 --> 00:01:27,380 we can specify a set of timezones, 23 00:01:27,380 --> 00:01:30,560 and then convert time between any pair of them. 24 00:01:30,560 --> 00:01:36,840 To do that, I firstly set a variable TZONES to the timezones I'm interested in: 25 00:01:36,840 --> 00:01:40,200 Los Angeles, New York and London. 26 00:01:40,200 --> 00:01:44,500 Then I iterate through the values of TZONES twice; 27 00:01:44,500 --> 00:01:47,520 the first for loop uses the timezones as input, 28 00:01:47,520 --> 00:01:49,600 and stores them in tzin, 29 00:01:49,600 --> 00:01:54,480 while the second for loop has them stored as output in tzout. 30 00:01:54,480 --> 00:01:56,340 Using the function showtime, 31 00:01:56,340 --> 00:02:02,580 I now convert the time 11:45 for each tzin to each tzout. 32 00:02:02,580 --> 00:02:10,860 The result we see is a conversion of 11:45 between all timezones. 33 00:02:10,860 --> 00:02:14,560 The "if" command allows us to execute various other commands, 34 00:02:14,560 --> 00:02:16,640 based on the result of a condition; 35 00:02:16,640 --> 00:02:22,980 the result of whether another command succeeded or not. 36 00:02:23,680 --> 00:02:26,600 As a demonstration, I create two files: 37 00:02:26,600 --> 00:02:29,780 the sourcefile and the destfile, with a bit of delay, 38 00:02:29,780 --> 00:02:34,460 so that the destfile is clearly more recent than the sourcefile. 39 00:02:34,460 --> 00:02:36,740 To refresh a file based on another, 40 00:02:36,740 --> 00:02:39,940 for example to refresh the destfile based on sourcefile, 41 00:02:39,950 --> 00:02:45,080 I run: if test sourcefile -nt destfile; 42 00:02:45,080 --> 00:02:48,040 using the test command with the nt option, 43 00:02:48,040 --> 00:02:53,220 to check whether the sourcefile is newer than the destfile. 44 00:02:53,220 --> 00:02:57,160 In case it is newer, sourcefile is copied to the destfile, 45 00:02:57,160 --> 00:03:00,520 and I echo that the destfile has been refreshed. 46 00:03:00,520 --> 00:03:05,540 Apparently, nothing happens because the sourcefile is older than the destfile, 47 00:03:05,540 --> 00:03:10,520 and, as a result, the destfile is not refreshed. 48 00:03:10,520 --> 00:03:12,480 To verify the opposite case, 49 00:03:12,480 --> 00:03:16,280 I recreate the sourcefile to be newer than the destfile 50 00:03:16,280 --> 00:03:18,060 and run the command again, 51 00:03:18,060 --> 00:03:22,080 this time using an alternative syntax for the test command: 52 00:03:22,080 --> 00:03:25,200 a set of square brackets. 53 00:03:25,200 --> 00:03:28,960 Again I test whether the sourcefile is newer than the destfile, 54 00:03:28,960 --> 00:03:32,440 and repeat the same copy and echo commands. 55 00:03:32,440 --> 00:03:36,140 We notice that the destfile is actually refreshed this time, 56 00:03:36,140 --> 00:03:40,060 because it is older than the sourcefile. 57 00:03:40,060 --> 00:03:44,300 The if command can also be combined with an else case. 58 00:03:44,300 --> 00:03:49,820 In a similar example, I test whether the sourcefile is newer than the destfile, 59 00:03:49,820 --> 00:03:56,580 and after writing the same copy and echo commands, 60 00:03:58,980 --> 00:04:01,180 I add an else command. 61 00:04:01,180 --> 00:04:06,320 In case the condition is false, I echo that the destfile is up to date. 62 00:04:06,320 --> 00:04:12,600 As expected, the destfile is shown to be up to date. 63 00:04:14,540 --> 00:04:21,860 The "while" command allows us to iterate while a condition holds. 64 00:04:21,860 --> 00:04:26,900 Let's now write a small script that counts the average number of characters per line, 65 00:04:26,900 --> 00:04:30,940 for all readable files in the etc directory. 66 00:04:30,940 --> 00:04:36,100 First, I list the directory entries redirecting the output of ls, 67 00:04:36,100 --> 00:04:38,840 with a pipe to a while loop. 68 00:04:38,840 --> 00:04:42,440 While takes as an argument another command, read, 69 00:04:42,440 --> 00:04:47,340 in order to read the input directory entries line by line. 70 00:04:47,340 --> 00:04:51,660 Each filename is stored in a variable called name. 71 00:04:51,660 --> 00:04:57,680 Then for each filename, I test with an if command, if it is a regular readable file, 72 00:04:57,680 --> 00:05:01,780 passing the -f option to check it's a regular file, 73 00:05:01,780 --> 00:05:07,700 the -a option for and, and the -r option to check if it's readable. 74 00:05:07,700 --> 00:05:12,260 In case the entry is an actual file and not a directory, and is readable, 75 00:05:12,260 --> 00:05:18,560 I display the filename without a newline, by passing the -n option to echo. 76 00:05:18,560 --> 00:05:21,220 Next, I run the expr command, 77 00:05:21,220 --> 00:05:26,060 to calculate the average number of characters per line in the particular file. 78 00:05:26,060 --> 00:05:32,560 Specifically, I count the characters of the file using wc -c and the file as input, 79 00:05:32,560 --> 00:05:38,540 along with the number of its lines using wc -l, and divide the two. 80 00:05:38,540 --> 00:05:45,340 I complete the if command with fi and the while loop with done, 81 00:05:45,340 --> 00:05:51,880 and pipe the output to head to see the first few lines. 82 00:05:52,900 --> 00:05:58,000 The "xargs" command is a magnificent tool for working with huge datasets. 83 00:05:58,000 --> 00:06:01,060 The command reads data from its standard input, 84 00:06:01,060 --> 00:06:05,680 and then supplies them as arguments to a specified command that it executes. 85 00:06:05,680 --> 00:06:08,540 It is needed, because the operating system, 86 00:06:08,540 --> 00:06:13,580 provides only a fixed amount of space for providing arguments to a command. 87 00:06:13,580 --> 00:06:18,360 Xargs overcomes this limit by executing the command repeatedly, 88 00:06:18,360 --> 00:06:24,260 with new batches of arguments chunked from its input. 89 00:06:24,260 --> 00:06:28,560 Consider as an example, that we want to find how many lines exist, 90 00:06:28,560 --> 00:06:34,500 in all files under a specific directory, in my case the current directory. 91 00:06:34,500 --> 00:06:37,280 I run the find command in the current directory, 92 00:06:37,280 --> 00:06:40,980 specifying with -type f that I'm interested in files, 93 00:06:40,980 --> 00:06:44,380 and then apply cat through xargs on all files, 94 00:06:44,380 --> 00:06:47,640 in order to concatenate the files together. 95 00:06:47,640 --> 00:06:56,020 Finally, I pipe the output of the wc -l command, to count the number of lines. 96 00:06:56,020 --> 00:07:00,200 Find doesn't always work as expected with xargs. 97 00:07:00,200 --> 00:07:03,860 For instance, let's say I want to find the oldest file, 98 00:07:03,860 --> 00:07:07,820 of the directory "Program Files" on a Windows machine. 99 00:07:07,820 --> 00:07:14,100 To do that I run find for "Program Files" and type file, 100 00:07:14,100 --> 00:07:18,920 I apply stat to get the modification time of these files, 101 00:07:18,920 --> 00:07:25,700 sort through the output numerically, and display the oldest file using head -1. 102 00:07:25,700 --> 00:07:28,240 But, instead of the oldest file, 103 00:07:28,240 --> 00:07:33,340 we get various errors saying: cannot find Application or Verifier. 104 00:07:33,340 --> 00:07:36,400 The problem here is that the output of find, 105 00:07:36,400 --> 00:07:39,920 contains some lines with spaces, because on the Windows system, 106 00:07:39,920 --> 00:07:44,780 filenames often have their words separated by space. 107 00:07:44,780 --> 00:07:49,560 Since xargs uses both a newline and a space to break up its arguments, 108 00:07:49,560 --> 00:07:53,840 some filenames containing spaces are incorrectly broken apart, 109 00:07:53,840 --> 00:07:59,280 leading to the creation and display of non-existing files. 110 00:07:59,280 --> 00:08:03,340 To get around this, find offers the option -print0, 111 00:08:03,340 --> 00:08:07,020 which separates the output elements using a null character, 112 00:08:07,020 --> 00:08:08,440 instead of a newline, 113 00:08:08,440 --> 00:08:12,740 based on the fact that there are no nulls in filenames. 114 00:08:12,740 --> 00:08:17,660 The output of find is then piped to xargs using a -0 option, 115 00:08:17,660 --> 00:08:21,280 to specify that the input is separated by null characters, 116 00:08:21,280 --> 00:08:23,960 rather than newlines or spaces. 117 00:08:23,960 --> 00:08:28,640 Again, I output the modification time and the name of each file, 118 00:08:28,640 --> 00:08:30,780 sorting the results numerically, 119 00:08:30,780 --> 00:08:36,280 and retrieving the oldest file, which is a LICENCE file of gsview. 120 00:08:36,280 --> 00:08:40,180 By listing the file we verify that it was created 20 years ago, 121 00:08:40,180 --> 00:08:42,920 in the year 2000. 122 00:08:45,280 --> 00:08:48,840 The "case" command allows us to run a specific command, 123 00:08:48,840 --> 00:08:52,740 based on pattern matching. 124 00:08:53,540 --> 00:08:56,800 To see this in action I create some command aliases, 125 00:08:56,800 --> 00:09:00,300 that depend on the operating system I currently run. 126 00:09:00,300 --> 00:09:02,580 These are useful to make our commands work, 127 00:09:02,580 --> 00:09:05,900 independently of the system we use. 128 00:09:05,900 --> 00:09:10,120 The uname command displays the name of the current operating system, 129 00:09:10,120 --> 00:09:12,540 which in my case is Linux. 130 00:09:12,540 --> 00:09:15,960 With a case statement I use the output of uname, 131 00:09:15,960 --> 00:09:20,560 and then insert an "in" and some patterns that specify what happens, 132 00:09:20,560 --> 00:09:23,200 depending on the value of uname. 133 00:09:23,200 --> 00:09:26,780 In case of Linux, I create a command alias s, 134 00:09:26,780 --> 00:09:30,260 which starts a document using gnome-open. 135 00:09:30,260 --> 00:09:33,500 Then, I create a second alias cpt, 136 00:09:33,500 --> 00:09:36,400 that copies the current directory to the clipboard, 137 00:09:36,400 --> 00:09:43,760 using pwd to print the current directory, a pipe, and xsel --clipboard. 138 00:09:43,760 --> 00:09:47,940 With a double semicolon I end the first case. 139 00:09:47,940 --> 00:09:50,580 I then start another one for Darwin, 140 00:09:50,580 --> 00:09:55,040 which is the output of uname on an Apple macOS machine. 141 00:09:55,040 --> 00:09:59,240 Similarly the alias s is set to "open" here, 142 00:09:59,240 --> 00:10:04,580 whereas cpt is set to "pwd | pbcopy". 143 00:10:04,580 --> 00:10:10,600 On the Cygwin, Unix-like environment for Windows, the aliases vary accordingly. 144 00:10:10,600 --> 00:10:15,060 Finally, after ending the case command I run alias, 145 00:10:15,060 --> 00:10:21,060 and verify that the aliases cpt and s correspond to the Linux case. 146 00:10:21,060 --> 00:10:29,620 So cpt contains "pwd | xsel", while s is set to "gnome-open". 147 00:10:31,300 --> 00:10:36,760 This concludes our foundations unit on execution control; stay with us!