What is a Terminal?
Much of the terminology used when discussing modern terminals comes from history, which can make understanding terminals seem more confusing than it should be. A small amount history can help provide some background information to help some of the concepts be more relatable. Early computers were comprised of multiple refrigerator-sized cabinets, each of which performed one of the basic tasks of the system. For example, one cabinet would contain the "central processing unit " (CPU), while other cabinets held tape drives , disk drives , punched-card readers , a line printer , and any other peripherals that were available to the computer. The user-interface to these computers consisted of a keyboard and some means of displaying the output from the computer, such as a sequence of light bulbs or later a Cathode-ray Tube (CRT) display , connected to the computer via cables. The user-interface was typically arranged into a "console ", where the operator could sit and interact with the computer. Since the user-interface represented the point at which electrical signals "enter" and "exit" the network, the user-interfaces themselves became known as "terminals ". Standardization As often happens in technology development, there were initially a wide range of competing technologies, but over time market forces and economies of scale led these competing technologies to converge towards standardization around a handful of those technologies, the names of which have become much of the jargon that surrounds the terminal today. Early terminals were electro-mechanical "Teletype writers", generally shortened to "TTY ", which is a term still used today when interacting with the terminal. As with a modern keyboard, each key on the TTY represented a human-readable character such as a letter, number, punctuation, etc. As the user would press a key to input data into the computer, each key would be "encoded " into a sequence of 8 1s and 0s called bytes , which could be understood by the computer. These bytes were sent over the cable to the CPU, which performed the requested operations, then the resulting byte(s) were transmitted back to the terminal where they were "decoded" back into characters for human-consumption. The rules used for encoding and decoding each character eventually became the ASCII standard. Despite the benefits of standardization, the ASCII standard was written to support US English, which led to several extensions in order to support other languages, which eventually led to the UTF-8 standard which unified the various extensions into a single encoding that is broadly in use today. Over time display technologies evolved which led to the introduction of "video terminals" (VT) which allowed text and other graphical information to be displayed on a screen. Some of the most popular video terminals were the VT100 and later VT200 series, which introduced support for the ANSI escape codes that have become standards that are still in use today. Emulation As microprocessor technology advanced and the cost of memory and other peripherals dropped, terminals began to handle increasingly-advanced operations, which led to the introduction of "intelligent terminals ", which differentiated them from the "dumb terminals" they replaced. In order to make the newer terminals "backward compatible " with existing software, these terminals included hardware to "emulate " the older devices. Over time the emulation functions were implemented in software, which eventually led to the introduction of fully-software "terminal emulators " that could offer features that would have been difficult to impossible to implement in hardware, such as command-line completion and syntax highlighting . With the introduction of terminal emulators, "the terminal" became a software window that is opened in order to gain access to the operating system . The software that provides this access is called "the shell", which is the subject of the next section.
What is the Shell?
As described in the previous section, when a terminal is opened it runs software that provides access to the computer's operating system. This software has became known as "the shell ". Let's take a quick at how that name came to be used. At a high level, operating systems are designed with two distinct parts: A first part which contains the low-level software that has complete control of system resources. A second part which "surrounds" the first to prevent access to it, except for specific, pre-defined actions that application software are allowed to take. This concept has been visualized as a nut contained within its shell, where the meat of the nut (the "kernel ") contains the low-level software, and the hard shell that surrounds it prevents outside access except for specific, pre-defined "system calls ", such as those defined by "the Linux API . With this in mind, when users open a terminal they are presented with an application that accepts commands that are typed in the terminal, executes them, and returns the output to the terminal interface. This application provides users an interface to make system calls, call other applications, and execute scripts that define higher-level interactions with the operating system. Since these "shell applications" allow users to interact directly with the shell, they have become synonymous with "the shell". On Linux systems, the most common shell application is Bash , which is often installed by default, though other popular shell applications include ksh , zsh , and fish . Since Bash is the most common shell we will use it for examples, but most shells feature backwards-compatibility with Bash so most examples should work across other shells. As terminals evolved from hardware to software, the boundaries between the terminal and the shell became increasingly blurred, to the point where the concepts of "the terminal", a "terminal emulator", "the console", and "the shell" have become interchangeable in many conversations, which has led to confusion about what each term actually refers to. Possibly the most common term for these concepts among users is "the command-line", which is the topic of the next section.
If Then Control Flow in Lua
Conditional statements control program flow by evaluating an expression then proceeding to one or more different "paths" depending on the result. Lua implements conditional statements with the if-then statement, which supports several variations. If-Then The most simple conditional statement contains a single boolean condition that directs program flow to a branch if that condition evaluates to true . if-then statements in Lua follow the pattern: if[booleanexpression]then -- execute when boolean expression is true end The following is a simple example of the if-then statement: localx=2 localy=3 localresult ifx>ythen result=x+y end print(result)-- nil It this example the values of the x and y variables are compared and, because x is not greater than y, the conditional branch is not executed, and result is not assigned a value. If-Then-Else In the previous example we created a block of code that is only executed if a specific condition exists. Sometimes we need a bit more control over program flow, and want program flow to take one of two paths, depending on the conditional. For this, Lua has the if-then-else statement, which follows the pattern: if[booleanexpression]then -- execute when boolean expression is true else -- execute when boolean expression is false end Let's look at a simple example: localx=2 localy=3 localresult ifx>ythen result=x+y else result=5 end print(result)-- 5 If-Then-Elseif-Else if[booleanexpression#1]then -- execute when boolean expression #1 is true elseif[booleanexpression#2]then -- execute when boolean expression #2 is true elseif[booleanexpression#n]then -- execute when boolean expression #n is true else-- optional -- execute when all boolean expressions are false end The expression can include any number of elseif clauses, allowing program flow to take any number of alternate paths. Note that the else clause is optional. When included this branch will execute only of all other conditionals are false. Let's extend our example to include a second conditional statement: localx=2 localy=3 localresult ifx>ythen result=x+y elseifx<ythen result=10 else result=5 end print(result)-- 10 Now that we have seen how if-then statements work, the next section will take a quick look at using the and and or to perform simple conditional statements.
Getting Help in Neovim
Neovim includes an extensive help system, which provides a significant amount of detail about virtually any vim-related topic. To open the help system, from Normal mode enter the command : :help Which splits the current window and displays a buffer containing the main help page. We will review splits shortly in the windows chapter. navigate the help window as you would any buffer , for example using j , k , etc. Help for a specific topic The help contents for a specific topic or command by adding the topic or command when invoking the help system. For example, to review the documentation for the help system itself: :help help Following links Help contents often contain links to other help topics. To review the linked content, move the cursor over the link and type C-] . Changing topics Once in the help system you can manually jump to other topics using the tag command: :tag [topic] where topic refers to the help topic you want to jump to. Returning to topics Return to the previous help topic by invoking C-T . Exiting the help window Exit the help window and return to the original window by typing C-W c or :quit .
Streams
We have seen how to execute commands , and how to build pipelines that pass information between commands to perform more complex tasks. In this section, we will touch on how this information is passed between commands, using "streams ". A stream refers to the information that flows through the pipeline, from command to command. A command receives information from the "input stream", processes it, then passes the result to the "output stream" (or possibly sends error information to the "error stream", if needed). To help solidify the concept of a stream, suppose there is a command that reads text from the input, modifies it in some way, then passes the result to the output. One might expect the process of executing that command to be: Read the input into memory Modify the content in memory Pass the modified content to the output However, consider two cases: Suppose that the input comes from a file that requires more memory than is available in the system. In this case it would be impossible to load the entire file into memory, and the command would fail. Suppose that the input comes from a continuous source, such as a temperature sensor that reads at regular intervals. Since the temperature is constantly being monitored, there is no such concept as "the end of the input". In order to handle these situations effectively, the process is a bit more like this: open the input source read a line of data if the end of the input stream is detected, stop processing and close the stream perform some operation on that line and pass the result to the output repeat from step 2 With this in mind, once the input stream is opened and a line is read, the command doesn't know how much data is contained in the input; it simply reads a line, processes it and passes the result to the output, then reads another until (possibly) all input data has been consumed. Similarly, the next command in the pipeline simply receives each line of data, processes it, then passes the result to its output. This flow of lines of data led this concept to be called a "stream". Standard Streams When any of input, output, or error are not specified, they each default to a specific "standard" stream. Under the hood, each stream is implemented as a file that can be read from or written to. There are three standard streams: Standard Input Standard input, called stdin and sometimes referenced numerically as "0", is the stream from which a program reads its input data, if not otherwise specified. Not all commands require an input stream. For example, the ls command, which displays information about files contained in a directory, reads input from the filesystem without any input data stream. Standard Output Standard output, called stdout and sometimes referenced numerically as "1", is the stream to which a program writes its output data. By default this is usually connected to the terminal, so that the results of a command are printed to the screen. Not all commands generate output. For example, the mv command, which renames a file on the filesystem, does not generate any output when it is successfully invoked. Standard Error Standard error, called stderr and sometimes referenced numerically as "2", is an alternative output stream that used by commands for error or diagnostic information. The main purpose of stderr is to allow a command to generate diagnostic feedback without polluting the output stream. Note This section is intended to provide just enough information to allow users to begin understanding and using streams in many common applications. If you want to dig a bit deeper into the streams and how they work a good place to start is Everything is a File . One of the useful features of the standard streams is that they can be replaced by other streams, combined etc, sent to in a process called redirection , which is the topic of the next section.
Commands
Overview At a high-level, command-line commands represent a piece of code that: Optionally receives input data, then Optionally performs an operation on that data, then Passes the (possibly modified) data to the output, and Optionally generates an error message. The following diagram provides a useful representation of this: The "natural" flow goes from the input, to the command, then to the output. If an error occurs, then information about that error is sent to the error. With this diagram in mind, let's proceed to see some examples that will help solidify this concept. Executing Commands Commands are executed from the command-line by typing the name of the command, possibly followed by options and/or arguments. Arguments provide the command with information that it needs to perform its task. For example, a command that operates on a file might be passed the name of the file to perform those operations on. Options provide "knobs" that allow the user to customize how the script behaves. The combination of command, arguments, and options can be combined into a call signature, which describes the format to be used when calling the command. As a simple example, the echo command is often called with a single string as an argument: ninja$: echo Hello World! Hello World! ninja$: which takes the argument (Hello World!) and passes it to the output which, by default, prints the output to the terminal screen. In the next section we will see how to use this output as the input for other commands.
head
The head command filters a stream so that only the beginning of the stream is passed to stdout . This is often used to get a quick summary of the contents of a file, or to target specific lines by index in a pipeline . The call signature is: head{options}{path} If path is specified, that file will be read and streamed to stdin . If path is omitted or set to -, then this command will operate on the stream that is piped or redirected to stdin. The most common option is -n (or the --lines long option), which defines the index of the line after which content is filtered. By default the index is 10. To demonstrate, let's work with our numbers.txt file, which contains a simple list of numbers from one through twelve: ninja$: head numbers.txt one two three four five six seven eight nine ten ninja$: Although this file contains 12 lines of text, only lines up to the 10th line are passed to stdout, Now lets change the index to 3: ninja$: head -n 3 numbers.txt one two three ninja$: The head command also allows the index to be negative, so that lines can be filtered relative to the end of the file. For example, to pass all lines except for the last 5 lines the index can be set to -5: ninja$: head -n -5 numbers.txt one two three four five six seven ninja$: