├── .editorconfig ├── .gitignore ├── .npmignore ├── README.md ├── bin └── cmd.js ├── package.json └── translations ├── pt-BR └── README.md ├── zh-CN └── README.md └── zh-TW └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [package.json] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bash-handbook [![CC 4.0][cc-image]][cc-url] 2 | 3 | This document was written for those who want to learn Bash without diving in too deeply. 4 | 5 | > **Tip**: Try [**learnyoubash**](https://git.io/learnyoubash) — an interactive workshopper based on this handbook! 6 | 7 | # Node Packaged Manuscript 8 | 9 | You can install this handbook using `npm`. Just run: 10 | 11 | ``` 12 | $ npm install -g bash-handbook 13 | ``` 14 | 15 | You should be able to run `bash-handbook` at the command line now. This will open the manual in your selected `$PAGER`. Otherwise, you may continue reading on here. 16 | 17 | The source is available here: 18 | 19 | # Translations 20 | 21 | Currently, there are these translations of **bash-handbook**: 22 | 23 | - [Português (Brasil)](/translations/pt-BR/README.md) 24 | - [简体中文 (中国)](/translations/zh-CN/README.md) 25 | - [繁體中文(台灣)](/translations/zh-TW/README.md) 26 | 27 | [**Request another translation**][tr-request] 28 | 29 | [tr-request]: https://github.com/denysdovhan/bash-handbook/issues/new?title=Translation%20Request:%20%5BPlease%20enter%20language%20here%5D&body=I%20am%20able%20to%20translate%20this%20language%20%5Byes/no%5D 30 | 31 | # Table of Contents 32 | 33 | - [Introduction](#introduction) 34 | - [Shells and modes](#shells-and-modes) 35 | - [Interactive](#interactive-mode) 36 | - [Non-interactive](#non-interactive-mode) 37 | - [Exit codes](#exit-codes) 38 | - [Comments](#comments) 39 | - [Variables](#variables) 40 | - [Local variables](#local-variables) 41 | - [Environment variables](#environment-variables) 42 | - [Positional parameters](#positional-parameters) 43 | - [Shell expansions](#shell-expansions) 44 | - [Brace expansion](#brace-expansion) 45 | - [Command substitution](#command-substitution) 46 | - [Arithmetic expansion](#arithmetic-expansion) 47 | - [Double and single quotes](#double-and-single-quotes) 48 | - [Arrays](#arrays) 49 | - [Array declaration](#array-declaration) 50 | - [Array expansion](#array-expansion) 51 | - [Array slice](#array-slice) 52 | - [Adding elements into an array](#adding-elements-into-an-array) 53 | - [Deleting elements from an array](#deleting-elements-from-an-array) 54 | - [Streams, pipes and lists](#streams-pipes-and-lists) 55 | - [Streams](#streams) 56 | - [Pipes](#pipes) 57 | - [Lists of commands](#lists-of-commands) 58 | - [Conditional statements](#conditional-statements) 59 | - [Primary and combining expressions](#primary-and-combining-expressions) 60 | - [Using an `if` statement](#using-an-if-statement) 61 | - [Using a `case` statement](#using-a-case-statement) 62 | - [Loops](#loops) 63 | - [`for` loop](#for-loop) 64 | - [`while` loop](#while-loop) 65 | - [`until` loop](#until-loop) 66 | - [`select` loop](#select-loop) 67 | - [Loop control](#loop-control) 68 | - [Functions](#functions) 69 | - [Debugging](#debugging) 70 | - [Afterword](#afterword) 71 | - [Other resources](#other-resources) 72 | - [License](#license) 73 | 74 | # Introduction 75 | 76 | If you are a developer, then you know the value of time. Optimizing your work process is one of the most important aspects of the job. 77 | 78 | In that path towards efficiency and productivity, we are often posed with actions that must be repeated over and over again, like: 79 | 80 | * taking a screenshot and uploading it to a server 81 | * processing text that may come in many shapes and forms 82 | * converting files between different formats 83 | * parsing a program's output 84 | 85 | Enter **Bash**, our savior. 86 | 87 | Bash is a Unix shell written by [Brian Fox][] for the GNU Project as a free software replacement for the [Bourne shell](https://en.wikipedia.org/wiki/Bourne_shell). It was released in 1989 and has been distributed as the Linux and OS X default shell for a long time. 88 | 89 | [Brian Fox]: https://en.wikipedia.org/wiki/Brian_Fox_(computer_programmer) 90 | 91 | 92 | So why do we need to learn something that was written more than 30 years ago? The answer is simple: this _something_ is today one of the most powerful and portable tools for writing efficient scripts for all Unix-based systems. And that's why you should learn bash. Period. 93 | 94 | In this handbook, I'm going to describe the most important concepts in bash with examples. I hope this compendium will be helpful to you. 95 | 96 | # Shells and modes 97 | 98 | The user bash shell can work in two modes - interactive and non-interactive. 99 | 100 | ## Interactive mode 101 | 102 | If you are working on Ubuntu, you have seven virtual terminals available to you. 103 | The desktop environment takes place in the seventh virtual terminal, so you can return to a friendly GUI 104 | using the `Ctrl-Alt-F7` keybinding. 105 | 106 | You can open the shell using the `Ctrl-Alt-F1` keybinding. After that, the familiar GUI will disappear and one of the virtual terminals will be shown. 107 | 108 | If you see something like this, then you are working in interactive mode: 109 | 110 | user@host:~$ 111 | 112 | Here you can enter a variety of Unix commands, such as `ls`, `grep`, `cd`, `mkdir`, `rm` and see the result of their execution. 113 | 114 | We call this shell interactive because it interacts directly with the user. 115 | 116 | Using a virtual terminal is not really convenient. For example, if you want to edit a document and execute another command at the same time, you are better off using virtual terminal emulators like: 117 | 118 | - [GNOME Terminal](https://en.wikipedia.org/wiki/GNOME_Terminal) 119 | - [Terminator](https://en.wikipedia.org/wiki/Terminator_(terminal_emulator)) 120 | - [iTerm2](https://en.wikipedia.org/wiki/ITerm2) 121 | - [ConEmu](https://en.wikipedia.org/wiki/ConEmu) 122 | 123 | ## Non-interactive mode 124 | 125 | In non-interactive mode, the shell reads commands from a file or a pipe and executes them. When the interpreter reaches the end of the file, the shell process terminates the session and returns to the parent process. 126 | 127 | Use the following commands for running the shell in non-interactive mode: 128 | 129 | sh /path/to/script.sh 130 | bash /path/to/script.sh 131 | 132 | In the example above, `script.sh` is just a regular text file that consists of commands the shell interpreter can evaluate and `sh` or `bash` is the shell's interpreter program. You can create `script.sh` using your preferred text editor (e.g. vim, nano, Sublime Text, Atom, etc). 133 | 134 | You can also simplify invoking the script by making it an executable file using the `chmod` command: 135 | 136 | 137 | chmod +x /path/to/script.sh 138 | 139 | Additionally, the first line in the script must indicate which program it should use to run the file, like so: 140 | 141 | ```bash 142 | #!/bin/bash 143 | echo "Hello, world!" 144 | ``` 145 | 146 | Or if you prefer to use `sh` instead of `bash`, change `#!/bin/bash` to `#!/bin/sh`. This `#!` character sequence is known as the [shebang](http://en.wikipedia.org/wiki/Shebang_%28Unix%29). Now you can run the script like this: 147 | 148 | /path/to/script.sh 149 | 150 | A handy trick we used above is using `echo` to print text to the terminal screen. 151 | 152 | Another way to use the shebang line is as follows: 153 | 154 | ```bash 155 | #!/usr/bin/env bash 156 | echo "Hello, world!" 157 | ``` 158 | 159 | The advantage of this shebang line is it will search for the program (in this case `bash`) based on the `PATH` environment variable. This is often preferred over the first method shown above, as the location of a program on a filesystem cannot always be assumed. This is also useful if the `PATH` variable on a system has been configured to point to an alternate version of the program. For instance, one might install a newer version of `bash` while preserving the original version and insert the location of the newer version into the `PATH` variable. The use of `#!/bin/bash` would result in using the original `bash`, while `#!/usr/bin/env bash` would make use of the newer version. 160 | 161 | 162 | ## Exit codes 163 | 164 | Every command returns an **exit code** (**return status** or **exit status**). A successful command always returns `0` (zero-code), and a command that has failed returns a non-zero value (error code). Failure codes must be positive integers between 1 and 255. 165 | 166 | Another handy command we can use when writing a script is `exit`. This command is used to terminate the current execution and deliver an exit code to the shell. Running an `exit` code without any arguments, will terminate the running script and return the exit code of the last command executed before `exit`. 167 | 168 | When a program terminates, the shell assigns its **exit code** to the `$?` environment variable. The `$?` variable is how we usually test whether a script has succeeded or not in its execution. 169 | 170 | In the same way we can use `exit` to terminate a script, we can use the `return` command to exit a function and return an **exit code** to the caller. You can use `exit` inside a function too and this will exit the function _and_ terminate the program. 171 | 172 | # Comments 173 | 174 | Scripts may contain _comments_. Comments are special statements ignored by the `shell` interpreter. They begin with a `#` symbol and continue on to the end of the line. 175 | 176 | For example: 177 | 178 | ```bash 179 | #!/bin/bash 180 | # This script will print your username. 181 | whoami 182 | ``` 183 | 184 | > **Tip**: Use comments to explain what your script does and _why_. 185 | 186 | # Variables 187 | 188 | Like in most programming languages, you can also create variables in bash. 189 | 190 | Bash knows no data types. Variables can contain only numbers or a string of one or more characters. There are three kinds of variables you can create: local variables, environment variables and variables as _positional arguments_. 191 | 192 | ## Local variables 193 | 194 | **Local variables** are variables that exist only within a single script. They are inaccessible to other programs and scripts. 195 | 196 | A local variable can be declared using `=` sign (as a rule, there **should not** be any spaces between a variable's name, `=` and its value) and its value can be retrieved using the `$` sign. For example: 197 | 198 | ```bash 199 | username="denysdovhan" # declare variable 200 | echo $username # display value 201 | unset username # delete variable 202 | ``` 203 | 204 | We can also declare a variable local to a single function using the `local` keyword. Doing so causes the variable to disappear when the function exits. 205 | 206 | ```bash 207 | local local_var="I'm a local value" 208 | ``` 209 | 210 | ## Environment variables 211 | 212 | **Environment variables** are variables accessible to any program or script running in current shell session. They are created just like local variables, but using the keyword `export` instead. 213 | 214 | ```bash 215 | export GLOBAL_VAR="I'm a global variable" 216 | ``` 217 | 218 | There are _a lot_ of global variables in bash. You will meet these variables fairly often, so here is a quick lookup table with the most practical ones: 219 | 220 | | Variable | Description | 221 | | :----------- | :------------------------------------------------------------ | 222 | | `$HOME` | The current user's home directory. | 223 | | `$PATH` | A colon-separated list of directories in which the shell looks for commands. | 224 | | `$PWD` | The current working directory. | 225 | | `$RANDOM` | Random integer between 0 and 32767. | 226 | | `$UID` | The numeric, real user ID of the current user. | 227 | | `$PS1` | The primary prompt string. | 228 | | `$PS2` | The secondary prompt string. | 229 | 230 | Follow [this link](http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html#sect_03_02_04) to see an extended list of environment variables in Bash. 231 | 232 | ## Positional parameters 233 | 234 | **Positional parameters** are variables allocated when a function is evaluated and are given positionally. The following table lists positional parameter variables and other special variables and their meanings when you are inside a function. 235 | 236 | | Parameter | Description | 237 | | :------------- | :---------------------------------------------------------- | 238 | | `$0` | Script's name. | 239 | | `$1 … $9` | The parameter list elements from 1 to 9. | 240 | | `${10} … ${N}` | The parameter list elements from 10 to N. | 241 | | `$*` or `$@` | All positional parameters except `$0`. | 242 | | `$#` | The number of parameters, not counting `$0`. | 243 | | `$FUNCNAME` | The function name (has a value only inside a function). | 244 | 245 | In the example below, the positional parameters will be `$0='./script.sh'`, `$1='foo'` and `$2='bar'`: 246 | 247 | ./script.sh foo bar 248 | 249 | Variables may also have _default_ values. We can define as such using the following syntax: 250 | 251 | ```bash 252 | # if variables are empty, assign them default values 253 | : ${VAR:='default'} 254 | : ${$1:='first'} 255 | # or 256 | FOO=${FOO:-'default'} 257 | ``` 258 | 259 | # Shell expansions 260 | 261 | _Expansions_ are performed on the command line after it has been split into _tokens_. In other words, these expansions are a mechanism to calculate arithmetical operations, to save results of commands' executions and so on. 262 | 263 | If you are interested, you can read [more about shell expansions](https://www.gnu.org/software/bash/manual/bash.html#Shell-Expansions). 264 | 265 | ## Brace expansion 266 | 267 | Brace expansion allows us to generate arbitrary strings. It's similar to _filename expansion_. For example: 268 | 269 | ```bash 270 | echo beg{i,a,u}n # begin began begun 271 | ``` 272 | 273 | Also brace expansions may be used for creating ranges, which are iterated over in loops. 274 | 275 | ```bash 276 | echo {0..5} # 0 1 2 3 4 5 277 | echo {00..8..2} # 00 02 04 06 08 278 | ``` 279 | 280 | ## Command substitution 281 | 282 | Command substitution allow us to evaluate a command and substitute its value into another command or variable assignment. Command substitution is performed when a command is enclosed by ``` `` ``` or `$()`. For example, we can use it as follows: 283 | 284 | ```bash 285 | now=`date +%T` 286 | # or 287 | now=$(date +%T) 288 | 289 | echo $now # 19:08:26 290 | ``` 291 | 292 | ## Arithmetic expansion 293 | 294 | In bash we are free to do any arithmetical operations. But the expression must enclosed by `$(( ))` The format for arithmetic expansions is: 295 | 296 | ```bash 297 | result=$(( ((10 + 5*3) - 7) / 2 )) 298 | echo $result # 9 299 | ``` 300 | 301 | Within arithmetic expansions, variables should generally be used without a `$` prefix: 302 | 303 | ```bash 304 | x=4 305 | y=7 306 | echo $(( x + y )) # 11 307 | echo $(( ++x + y++ )) # 12 308 | echo $(( x + y )) # 13 309 | ``` 310 | 311 | ## Double and single quotes 312 | 313 | There is an important difference between double and single quotes. Inside double quotes variables or command substitutions are expanded. Inside single quotes they are not. For example: 314 | 315 | ```bash 316 | echo "Your home: $HOME" # Your home: /Users/ 317 | echo 'Your home: $HOME' # Your home: $HOME 318 | ``` 319 | 320 | Take care to expand local variables and environment variables within quotes if they could contain whitespace. As an innocuous example, consider using `echo` to print some user input: 321 | 322 | ```bash 323 | INPUT="A string with strange whitespace." 324 | echo $INPUT # A string with strange whitespace. 325 | echo "$INPUT" # A string with strange whitespace. 326 | ``` 327 | 328 | The first `echo` is invoked with 5 separate arguments — $INPUT is split into separate words, `echo` prints a single space character between each. In the second case, `echo` is invoked with a single argument (the entire $INPUT value, including whitespace). 329 | 330 | Now consider a more serious example: 331 | 332 | ```bash 333 | FILE="Favorite Things.txt" 334 | cat $FILE # attempts to print 2 files: `Favorite` and `Things.txt` 335 | cat "$FILE" # prints 1 file: `Favorite Things.txt` 336 | ``` 337 | 338 | While the issue in this example could be resolved by renaming FILE to `Favorite-Things.txt`, consider input coming from an environment variable, a positional parameter, or the output of another command (`find`, `cat`, etc). If the input *might* contain whitespace, take care to wrap the expansion in quotes. 339 | 340 | # Arrays 341 | 342 | Like in other programming languages, an array in bash is a variable that allows you to refer to multiple values. In bash, arrays are also zero-based, that is, the first element in an array has index 0. 343 | 344 | When dealing with arrays, we should be aware of the special environment variable `IFS`. **IFS**, or **Input Field Separator**, is the character that separates elements in an array. The default value is an empty space `IFS=' '`. 345 | 346 | ## Array declaration 347 | 348 | In bash you create an array by simply assigning a value to an index in the array variable: 349 | 350 | ```bash 351 | fruits[0]=Apple 352 | fruits[1]=Pear 353 | fruits[2]=Plum 354 | ``` 355 | 356 | Array variables can also be created using compound assignments such as: 357 | 358 | ```bash 359 | fruits=(Apple Pear Plum) 360 | ``` 361 | 362 | ## Array expansion 363 | 364 | Individual array elements are expanded similar to other variables: 365 | 366 | ```bash 367 | echo ${fruits[1]} # Pear 368 | ``` 369 | 370 | The entire array can be expanded by using `*` or `@` in place of the numeric index: 371 | 372 | ```bash 373 | echo ${fruits[*]} # Apple Pear Plum 374 | echo ${fruits[@]} # Apple Pear Plum 375 | ``` 376 | 377 | There is an important (and subtle) difference between the two lines above: consider an array element containing whitespace: 378 | 379 | ```bash 380 | fruits[0]=Apple 381 | fruits[1]="Desert fig" 382 | fruits[2]=Plum 383 | ``` 384 | 385 | We want to print each element of the array on a separate line, so we try to use the `printf` builtin: 386 | 387 | ```bash 388 | printf "+ %s\n" ${fruits[*]} 389 | # + Apple 390 | # + Desert 391 | # + fig 392 | # + Plum 393 | ``` 394 | 395 | Why were `Desert` and `fig` printed on separate lines? Let's try to use quoting: 396 | 397 | ```bash 398 | printf "+ %s\n" "${fruits[*]}" 399 | # + Apple Desert fig Plum 400 | ``` 401 | 402 | Now everything is on one line — that's not what we wanted! Here's where `${fruits[@]}` comes into play: 403 | 404 | ```bash 405 | printf "+ %s\n" "${fruits[@]}" 406 | # + Apple 407 | # + Desert fig 408 | # + Plum 409 | ``` 410 | 411 | Within double quotes, `${fruits[@]}` expands to a separate argument for each element in the array; whitespace in the array elements is preserved. 412 | 413 | ## Array slice 414 | 415 | Besides, we can extract a slice of array using the _slice_ operators: 416 | 417 | ```bash 418 | echo ${fruits[@]:0:2} # Apple Desert fig 419 | ``` 420 | 421 | In the example above, `${fruits[@]}` expands to the entire contents of the array, and `:0:2` extracts the slice of length 2, that starts at index 0. 422 | 423 | ## Adding elements into an array 424 | 425 | Adding elements into an array is quite simple too. Compound assignments are specially useful in this case. We can use them like this: 426 | 427 | ```bash 428 | fruits=(Orange "${fruits[@]}" Banana Cherry) 429 | echo ${fruits[@]} # Orange Apple Desert fig Plum Banana Cherry 430 | ``` 431 | 432 | The example above, `${fruits[@]}` expands to the entire contents of the array and substitutes it into the compound assignment, then assigns the new value into the `fruits` array mutating its original value. 433 | 434 | ## Deleting elements from an array 435 | 436 | To delete an element from an array, use the `unset` command: 437 | 438 | ```bash 439 | unset fruits[0] 440 | echo ${fruits[@]} # Apple Desert fig Plum Banana Cherry 441 | ``` 442 | 443 | # Streams, pipes and lists 444 | 445 | Bash has powerful tools for working with other programs and their outputs. Using streams we can send the output of a program into another program or file and thereby write logs or whatever we want. 446 | 447 | Pipes give us opportunity to create conveyors and control the execution of commands. 448 | 449 | It is paramount we understand how to use this powerful and sophisticated tool. 450 | 451 | ## Streams 452 | 453 | Bash receives input and sends output as sequences or **streams** of characters. These streams may be redirected into files or one into another. 454 | 455 | There are three descriptors: 456 | 457 | | Code | Descriptor | Description | 458 | | :--: | :--------: | :------------------- | 459 | | `0` | `stdin` | The standard input. | 460 | | `1` | `stdout` | The standard output. | 461 | | `2` | `stderr` | The errors output. | 462 | 463 | Redirection makes it possible to control where the output of a command goes to, and where the input of a command comes from. For redirecting streams these operators are used: 464 | 465 | | Operator | Description | 466 | | :------: | :------------------------------------------- | 467 | | `>` | Redirecting output | 468 | | `&>` | Redirecting output and error output | 469 | | `&>>` | Appending redirected output and error output | 470 | | `<` | Redirecting input | 471 | | `<<` | [Here documents](http://tldp.org/LDP/abs/html/here-docs.html) syntax | 472 | | `<<<` | [Here strings](http://www.tldp.org/LDP/abs/html/x17837.html) | 473 | 474 | Here are few examples of using redirections: 475 | 476 | ```bash 477 | # output of ls will be written to list.txt 478 | ls -l > list.txt 479 | 480 | # append output to list.txt 481 | ls -a >> list.txt 482 | 483 | # all errors will be written to errors.txt 484 | grep da * 2> errors.txt 485 | 486 | # read from errors.txt 487 | less < errors.txt 488 | ``` 489 | 490 | ## Pipes 491 | 492 | We could redirect standard streams not only in files, but also to other programs. **Pipes** let us use the output of a program as the input of another. 493 | 494 | In the example below, `command1` sends its output to `command2`, which then passes it on to the input of `command3`: 495 | 496 | command1 | command2 | command3 497 | 498 | Constructions like this are called **pipelines**. 499 | 500 | In practice, this can be used to process data through several programs. For example, here the output of `ls -l` is sent to the `grep` program, which prints only files with a `.md` extension, and this output is finally sent to the `less` program: 501 | 502 | ls -l | grep .md$ | less 503 | 504 | The exit status of a pipeline is normally the exit status of the last command in the pipeline. The shell will not return a status until all the commands in the pipeline have completed. If you want your pipelines to be considered a failure if any of the commands in the pipeline fail, you should set the pipefail option with: 505 | 506 | set -o pipefail 507 | 508 | ## Lists of commands 509 | 510 | A **list of commands** is a sequence of one or more pipelines separated by `;`, `&`, `&&` or `||` operator. 511 | 512 | If a command is terminated by the control operator `&`, the shell executes the command asynchronously in a subshell. In other words, this command will be executed in the background. 513 | 514 | Commands separated by a `;` are executed sequentially: one after another. The shell waits for the finish of each command. 515 | 516 | ```bash 517 | # command2 will be executed after command1 518 | command1 ; command2 519 | 520 | # which is the same as 521 | command1 522 | command2 523 | ``` 524 | 525 | Lists separated by `&&` and `||` are called _AND_ and _OR_ lists, respectively. 526 | 527 | The _AND-list_ looks like this: 528 | 529 | ```bash 530 | # command2 will be executed if, and only if, command1 finishes successfully (returns 0 exit status) 531 | command1 && command2 532 | ``` 533 | 534 | The _OR-list_ has the form: 535 | 536 | ```bash 537 | # command2 will be executed if, and only if, command1 finishes unsuccessfully (returns code of error) 538 | command1 || command2 539 | ``` 540 | 541 | The return code of an _AND_ or _OR_ list is the exit status of the last executed command. 542 | 543 | # Conditional statements 544 | 545 | Like in other languages, Bash conditionals let us decide to perform an action or not. The result is determined by evaluating an expression, which should be enclosed in `[[ ]]`. 546 | 547 | Conditional expression may contain `&&` and `||` operators, which are _AND_ and _OR_ accordingly. Besides this, there many [other handy expressions](#primary-and-combining-expressions). 548 | 549 | There are two different conditional statements: `if` statement and `case` statement. 550 | 551 | ## Primary and combining expressions 552 | 553 | Expressions enclosed inside `[[ ]]` (or `[ ]` for `sh`) are called **test commands** or **primaries**. These expressions help us to indicate results of a conditional. In the tables below, we are using `[ ]`, because it works for `sh` too. Here is an answer about [the difference between double and single square brackets in bash](http://serverfault.com/a/52050). 554 | 555 | **Working with the file system:** 556 | 557 | | Primary | Meaning | 558 | | :-----------: | :----------------------------------------------------------- | 559 | | `[ -e FILE ]` | True if `FILE` **e**xists. | 560 | | `[ -f FILE ]` | True if `FILE` exists and is a regular **f**ile. | 561 | | `[ -d FILE ]` | True if `FILE` exists and is a **d**irectory. | 562 | | `[ -s FILE ]` | True if `FILE` exists and not empty (**s**ize more than 0). | 563 | | `[ -r FILE ]` | True if `FILE` exists and is **r**eadable. | 564 | | `[ -w FILE ]` | True if `FILE` exists and is **w**ritable. | 565 | | `[ -x FILE ]` | True if `FILE` exists and is e**x**ecutable. | 566 | | `[ -L FILE ]` | True if `FILE` exists and is symbolic **l**ink. | 567 | | `[ FILE1 -nt FILE2 ]` | FILE1 is **n**ewer **t**han FILE2. | 568 | | `[ FILE1 -ot FILE2 ]` | FILE1 is **o**lder **t**han FILE2. | 569 | 570 | **Working with strings:** 571 | 572 | | Primary | Meaning | 573 | | :------------: | :---------------------------------------------------------- | 574 | | `[ -z STR ]` | `STR` is empty (the length is **z**ero). | 575 | | `[ -n STR ]` |`STR` is not empty (the length is **n**on-zero). | 576 | | `[ STR1 == STR2 ]` | `STR1` and `STR2` are equal. | 577 | | `[ STR1 != STR2 ]` | `STR1` and `STR2` are not equal. | 578 | 579 | **Arithmetic binary operators:** 580 | 581 | | Primary | Meaning | 582 | | :-----------------: | :----------------------------------------------------- | 583 | | `[ ARG1 -eq ARG2 ]` | `ARG1` is **eq**ual to `ARG2`. | 584 | | `[ ARG1 -ne ARG2 ]` | `ARG1` is **n**ot **e**qual to `ARG2`. | 585 | | `[ ARG1 -lt ARG2 ]` | `ARG1` is **l**ess **t**han `ARG2`. | 586 | | `[ ARG1 -le ARG2 ]` | `ARG1` is **l**ess than or **e**qual to `ARG2`. | 587 | | `[ ARG1 -gt ARG2 ]` | `ARG1` is **g**reater **t**han `ARG2`. | 588 | | `[ ARG1 -ge ARG2 ]` | `ARG1` is **g**reater than or **e**qual to `ARG2`. | 589 | 590 | Conditions may be combined using these **combining expressions:** 591 | 592 | | Operation | Effect | 593 | | :------------: | :---------------------------------------------------------- | 594 | | `[ ! EXPR ]` | True if `EXPR` is false. | 595 | | `[ (EXPR) ]` | Returns the value of `EXPR`. | 596 | | `[ EXPR1 -a EXPR2 ]` | Logical _AND_. True if `EXPR1` **a**nd `EXPR2` are true. | 597 | | `[ EXPR1 -o EXPR2 ]` | Logical _OR_. True if `EXPR1` **o**r `EXPR2` are true.| 598 | 599 | Sure, there are more useful primaries and you can easily find them in the [Bash man pages](http://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html). 600 | 601 | ## Using an `if` statement 602 | 603 | `if` statements work the same as in other programming languages. If the expression within the braces is true, the code between `then` and `fi` is executed. `fi` indicates the end of the conditionally executed code. 604 | 605 | ```bash 606 | # Single-line 607 | if [[ 1 -eq 1 ]]; then echo "true"; fi 608 | 609 | # Multi-line 610 | if [[ 1 -eq 1 ]]; then 611 | echo "true" 612 | fi 613 | ``` 614 | 615 | Likewise, we could use an `if..else` statement such as: 616 | 617 | ```bash 618 | # Single-line 619 | if [[ 2 -ne 1 ]]; then echo "true"; else echo "false"; fi 620 | 621 | # Multi-line 622 | if [[ 2 -ne 1 ]]; then 623 | echo "true" 624 | else 625 | echo "false" 626 | fi 627 | ``` 628 | 629 | Sometimes `if..else` statements are not enough to do what we want to do. In this case we shouldn't forget about the existence of `if..elif..else` statements, which always come in handy. 630 | 631 | Look at the example below: 632 | 633 | ```bash 634 | if [[ `uname` == "Adam" ]]; then 635 | echo "Do not eat an apple!" 636 | elif [[ `uname` == "Eva" ]]; then 637 | echo "Do not take an apple!" 638 | else 639 | echo "Apples are delicious!" 640 | fi 641 | ``` 642 | 643 | ## Using a `case` statement 644 | 645 | If you are confronted with a couple of different possible actions to take, then using a `case` statement may be more useful than nested `if` statements. For more complex conditions use `case` like below: 646 | 647 | ```bash 648 | case "$extension" in 649 | "jpg"|"jpeg") 650 | echo "It's image with jpeg extension." 651 | ;; 652 | "png") 653 | echo "It's image with png extension." 654 | ;; 655 | "gif") 656 | echo "Oh, it's a giphy!" 657 | ;; 658 | *) 659 | echo "Woops! It's not image!" 660 | ;; 661 | esac 662 | ``` 663 | 664 | Each case is an expression matching a pattern. The `|` sign is used for separating multiple patterns, and the `)` operator terminates a pattern list. The commands for the first match are executed. `*` is the pattern for anything else that doesn't match the defined patterns. Each block of commands should be divided with the `;;` operator. 665 | 666 | # Loops 667 | 668 | Here we won't be surprised. As in any programming language, a loop in bash is a block of code that iterates as long as the control conditional is true. 669 | 670 | There are four types of loops in Bash: `for`, `while`, `until` and `select`. 671 | 672 | ## `for` loop 673 | 674 | The `for` is very similar to its sibling in C. It looks like this: 675 | 676 | ```bash 677 | for arg in elem1 elem2 ... elemN 678 | do 679 | # statements 680 | done 681 | ``` 682 | 683 | During each pass through the loop, `arg` takes on the value from `elem1` to `elemN`. Values may also be wildcards or [brace expansions](#brace-expansion). 684 | 685 | Also, we can write `for` loop in one line, but in this case there needs to be a semicolon before `do`, like below: 686 | 687 | ```bash 688 | for i in {1..5}; do echo $i; done 689 | ``` 690 | 691 | By the way, if `for..in..do` seems a little bit weird to you, you can also write `for` in C-like style such as: 692 | 693 | ```bash 694 | for (( i = 0; i < 10; i++ )); do 695 | echo $i 696 | done 697 | ``` 698 | 699 | `for` is handy when we want to do the same operation over each file in a directory. For example, if we need to move all `.bash` files into the `script` folder and then give them execute permissions, our script would look like this: 700 | 701 | ```bash 702 | #!/bin/bash 703 | 704 | for FILE in $HOME/*.bash; do 705 | mv "$FILE" "${HOME}/scripts" 706 | chmod +x "${HOME}/scripts/${FILE}" 707 | done 708 | ``` 709 | 710 | ## `while` loop 711 | 712 | The `while` loop tests a condition and loops over a sequence of commands so long as that condition is _true_. A condition is nothing more than a [primary](#primary-and-combining-expressions) as used in `if..then` conditions. So a `while` loop looks like this: 713 | 714 | ```bash 715 | while [[ condition ]] 716 | do 717 | # statements 718 | done 719 | ``` 720 | 721 | Just like in the case of the `for` loop, if we want to write `do` and condition in the same line, then we must use a semicolon before `do`. 722 | 723 | A working example might look like this: 724 | 725 | ```bash 726 | #!/bin/bash 727 | 728 | # Squares of numbers from 0 through 9 729 | x=0 730 | while [[ $x -lt 10 ]]; do # value of x is less than 10 731 | echo $(( x * x )) 732 | x=$(( x + 1 )) # increase x 733 | done 734 | ``` 735 | 736 | ## `until` loop 737 | 738 | The `until` loop is the exact opposite of the `while` loop. Like a `while` it checks a test condition, but it keeps looping as long as this condition is _false_: 739 | 740 | ```bash 741 | until [[ condition ]]; do 742 | #statements 743 | done 744 | ``` 745 | 746 | ## `select` loop 747 | 748 | The `select` loop helps us to organize a user menu. It has almost the same syntax as the `for` loop: 749 | 750 | ```bash 751 | select answer in elem1 elem2 ... elemN 752 | do 753 | # statements 754 | done 755 | ``` 756 | 757 | The `select` prints all `elem1..elemN` on the screen with their sequence numbers, after that it prompts the user. Usually it looks like `$?` (`PS3` variable). The answer will be saved in `answer`. If `answer` is the number between `1..N`, then `statements` will execute and `select` will go to the next iteration — that's because we should use the `break` statement. 758 | 759 | A working example might look like this: 760 | 761 | ```bash 762 | #!/bin/bash 763 | 764 | PS3="Choose the package manager: " 765 | select ITEM in bower npm gem pip 766 | do 767 | echo -n "Enter the package name: " && read PACKAGE 768 | case $ITEM in 769 | bower) bower install $PACKAGE ;; 770 | npm) npm install $PACKAGE ;; 771 | gem) gem install $PACKAGE ;; 772 | pip) pip install $PACKAGE ;; 773 | esac 774 | break # avoid infinite loop 775 | done 776 | ``` 777 | 778 | This example, asks the user what package manager {s,he} would like to use. Then, it will ask what package we want to install and finally proceed to install it. 779 | 780 | If we run this, we will get: 781 | 782 | ``` 783 | $ ./my_script 784 | 1) bower 785 | 2) npm 786 | 3) gem 787 | 4) pip 788 | Choose the package manager: 2 789 | Enter the package name: bash-handbook 790 | 791 | ``` 792 | 793 | ## Loop control 794 | 795 | There are situations when we need to stop a loop before its normal ending or step over an iteration. In these cases, we can use the shell built-in `break` and `continue` statements. Both of these work with every kind of loop. 796 | 797 | The `break` statement is used to exit the current loop before its ending. We have already met with it. 798 | 799 | The `continue` statement steps over one iteration. We can use it as such: 800 | 801 | ```bash 802 | for (( i = 0; i < 10; i++ )); do 803 | if [[ $(( i % 2 )) -eq 0 ]]; then continue; fi 804 | echo $i 805 | done 806 | ``` 807 | 808 | If we run the example above, it will print all odd numbers from 0 through 9. 809 | 810 | # Functions 811 | 812 | In scripts we have the ability to define and call functions. As in any programming language, functions in bash are chunks of code, but there are differences. 813 | 814 | In bash, functions are a sequence of commands grouped under a single name, that is the _name_ of the function. Calling a function is the same as calling any other program, you just write the name and the function will be _invoked_. 815 | 816 | We can declare our own function this way: 817 | 818 | ```bash 819 | my_func () { 820 | # statements 821 | } 822 | 823 | my_func # call my_func 824 | ``` 825 | 826 | We must declare functions before we can invoke them. 827 | 828 | Functions can take on arguments and return a result — exit code. Arguments, within functions, are treated in the same manner as arguments given to the script in [non-interactive](#non-interactive-mode) mode — using [positional parameters](#positional-parameters). A result code can be _returned_ using the `return` command. 829 | 830 | Below is a function that takes a name and returns `0`, indicating successful execution. 831 | 832 | ```bash 833 | # function with params 834 | greeting () { 835 | if [[ -n $1 ]]; then 836 | echo "Hello, $1!" 837 | else 838 | echo "Hello, unknown!" 839 | fi 840 | return 0 841 | } 842 | 843 | greeting Denys # Hello, Denys! 844 | greeting # Hello, unknown! 845 | ``` 846 | 847 | We already discussed [exit codes](#exit-codes). The `return` command without any arguments returns the exit code of the last executed command. Above, `return 0` will return a successful exit code. `0`. 848 | 849 | ## Debugging 850 | 851 | The shell gives us tools for debugging scripts. If we want to run a script in debug mode, we use a special option in our script's shebang: 852 | 853 | ```bash 854 | #!/bin/bash options 855 | ``` 856 | 857 | These options are settings that change shell behavior. The following table is a list of options which might be useful to you: 858 | 859 | | Short | Name | Description | 860 | | :---: | :---------- | :----------------------------------------------------- | 861 | | `-f` | noglob | Disable filename expansion (globbing). | 862 | | `-i` | interactive | Script runs in _interactive_ mode. | 863 | | `-n` | noexec | Read commands, but don't execute them (syntax check). | 864 | | | pipefail | Make pipelines fail if any commands fail, not just if the final command fail. | 865 | | `-t` | — | Exit after first command. | 866 | | `-v` | verbose | Print each command to `stderr` before executing it. | 867 | | `-x` | xtrace | Print each command and its expanded arguments to `stderr` before executing it. | 868 | 869 | For example, we have script with `-x` option such as: 870 | 871 | ```bash 872 | #!/bin/bash -x 873 | 874 | for (( i = 0; i < 3; i++ )); do 875 | echo $i 876 | done 877 | ``` 878 | 879 | This will print the value of the variables to `stdout` along with other useful information: 880 | 881 | ``` 882 | $ ./my_script 883 | + (( i = 0 )) 884 | + (( i < 3 )) 885 | + echo 0 886 | 0 887 | + (( i++ )) 888 | + (( i < 3 )) 889 | + echo 1 890 | 1 891 | + (( i++ )) 892 | + (( i < 3 )) 893 | + echo 2 894 | 2 895 | + (( i++ )) 896 | + (( i < 3 )) 897 | ``` 898 | 899 | Sometimes we need to debug a part of a script. In this case using the `set` command is convenient. This command can enable and disable options. Options are turned on using `-` and turned off using `+`: 900 | 901 | ```bash 902 | #!/bin/bash 903 | 904 | echo "xtrace is turned off" 905 | set -x 906 | echo "xtrace is enabled" 907 | set +x 908 | echo "xtrace is turned off again" 909 | ``` 910 | 911 | # Afterword 912 | 913 | I hope this small handbook was interesting and helpful. To be honest, I wrote this handbook for myself so as to not forget the bash basics. I tried to write concisely but meaningfully, and I hope you will appreciate that. 914 | 915 | This handbook narrates my own experience with Bash. It does not purport to be comprehensive, so if you still want more, please run `man bash` and start there. 916 | 917 | Contributions are absolutely welcome and I will be grateful for any corrections or questions you can send my way. For all of that create a new [issue](https://github.com/denysdovhan/bash-handbook/issues). 918 | 919 | Thanks for reading this handbook! 920 | 921 | # Want to learn more? 922 | 923 | Here's a list of other literature covering Bash: 924 | 925 | * Bash man page. In many environments that you can run Bash, the help system `man` can display information about Bash, by running the command `man bash`. For more information on the `man` command, see the web page ["The man Command"](http://www.linfo.org/man.html) hosted at [The Linux Information Project](http://www.linfo.org/). 926 | * ["Bourne-Again SHell manual"](https://www.gnu.org/software/bash/manual/) in many formats, including HTML, Info, TeX, PDF, and Texinfo. Hosted at . As of 2016/01, this covers version 4.3, last updated 2015/02/02. 927 | 928 | # Other resources 929 | 930 | * [awesome-bash](https://github.com/awesome-lists/awesome-bash) is a curated list of Bash scripts and resources 931 | * [awesome-shell](https://github.com/alebcay/awesome-shell) is another curated list of shell resources 932 | * [bash-it](https://github.com/Bash-it/bash-it) provides a solid framework for using, developing and maintaining shell scripts and custom commands for your daily work. 933 | * [dotfiles.github.io](http://dotfiles.github.io/) is a good source of pointers to the various dotfiles collections and shell frameworks available for bash and other shells. 934 | * [learnyoubash](https://github.com/denysdovhan/learnyoubash) helps you write your first bash script 935 | * [shellcheck](https://github.com/koalaman/shellcheck) is a static analysis tool for shell scripts. You can either use it from a web page at [www.shellcheck.net](http://www.shellcheck.net/) or run it from the command line. Installation instructions are on the [koalaman/shellcheck](https://github.com/koalaman/shellcheck) github repository page. 936 | 937 | Finally, Stack Overflow has many questions that are [tagged as bash](https://stackoverflow.com/questions/tagged/bash) that you can learn from and is a good place to ask if you're stuck. 938 | 939 | # License 940 | 941 | [![CC 4.0][cc-image]][cc-url] © [Denys Dovhan](http://denysdovhan.com) 942 | 943 | [cc-url]: http://creativecommons.org/licenses/by/4.0/ 944 | [cc-image]: https://i.creativecommons.org/l/by/4.0/80x15.png 945 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var obj = require('through2').obj; 5 | var pager = require('default-pager'); 6 | var msee = require('msee'); 7 | var join = require('path').join; 8 | var boxen = require('boxen'); 9 | var chalk = require('chalk'); 10 | var updateNotifier = require('update-notifier'); 11 | var pkg = require('../package.json'); 12 | var meow = require('meow'); 13 | 14 | var cli = meow([ 15 | 'Usage', 16 | ' bash-handbook', 17 | '', 18 | 'Options', 19 | ' --lang, -l Translation language', 20 | '', 21 | 'Examples', 22 | ' bash-handbook', 23 | ' bash-handbook --lang pt-br' 24 | ], { 25 | string: [ 26 | 'lang' 27 | ], 28 | alias: { 29 | l: 'lang' 30 | }, 31 | default: { 32 | lang: '' 33 | } 34 | }); 35 | 36 | var boxenOpts = { 37 | borderColor: 'yellow', 38 | margin: { 39 | bottom: 1 40 | }, 41 | padding: { 42 | right: 1, 43 | left: 1 44 | } 45 | }; 46 | 47 | var mseeOpts = { 48 | paragraphEnd: '\n\n' 49 | }; 50 | 51 | var notifier = updateNotifier({ 52 | pkg: pkg 53 | }); 54 | 55 | process.env.PAGER = process.env.PAGER || 'less'; 56 | process.env.LESS = process.env.LESS || 'FRX'; 57 | 58 | var lang = cli.flags.lang.toLowerCase() 59 | .split('-') 60 | .map(function (l, i) { 61 | return i === 0 ? l : l.toUpperCase(); 62 | }) 63 | .join('-'); 64 | 65 | var translation = join(__dirname, !lang ? 66 | '../README.md' : 67 | '../translations/' + lang + '/README.md'); 68 | 69 | fs.stat(translation, function (err, stats) { 70 | if (err) { 71 | console.log('The %s translation does not exist', chalk.bold(lang)); 72 | return; 73 | } 74 | 75 | fs.createReadStream(translation) 76 | .pipe(obj(function (chunk, enc, cb) { 77 | var message = []; 78 | 79 | if (notifier.update) { 80 | message.push('Update available: ' + chalk.green.bold(notifier.update.latest) + chalk.dim(' (current: ' + notifier.update.current + ')')); 81 | message.push('Run ' + chalk.blue('npm install -g ' + pkg.name) + ' to update.'); 82 | this.push(boxen(message.join('\n'), boxenOpts)); 83 | } 84 | 85 | this.push(msee.parse(chunk.toString(), mseeOpts)); 86 | cb(); 87 | })) 88 | .pipe(pager()); 89 | }); 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bash-handbook", 3 | "version": "2.7.0", 4 | "description": "For those who wanna learn Bash", 5 | "bin": { 6 | "bash-handbook": "bin/cmd.js" 7 | }, 8 | "scripts": { 9 | "postpublish": "git push --follow-tags" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/denysdovhan/bash-handbook.git" 14 | }, 15 | "keywords": [ 16 | "bash", 17 | "guide", 18 | "handbook" 19 | ], 20 | "author": "Denys Dovhan (http://denysdovhan.com)", 21 | "license": "CC-BY-4.0", 22 | "bugs": { 23 | "url": "https://github.com/denysdovhan/bash-handbook/issues" 24 | }, 25 | "homepage": "https://github.com/denysdovhan/bash-handbook#readme", 26 | "dependencies": { 27 | "boxen": "^0.2.0", 28 | "chalk": "^1.1.1", 29 | "default-pager": "^1.1.0", 30 | "meow": "^3.7.0", 31 | "msee": "^0.2.0", 32 | "through2": "^2.0.0", 33 | "update-notifier": "^0.6.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /translations/pt-BR/README.md: -------------------------------------------------------------------------------- 1 | # Guia de Bolso do Bash (inglês, bash-handbook)[![CC 4.0][cc-image]][cc-url] 2 | 3 | Este documento foi escrito para aqueles que querem aprender Bash sem mergulhar muito profundamente. 4 | 5 | > **Dica**: Dê uma olhada em [**learnyoubash**](https://git.io/learnyoubash) — são exercícios interativos baseados nesse documento! 6 | 7 | # Instalando o guia através do Node 8 | 9 | Você pode instalar esse documento usando `npm`. Execute: 10 | 11 | ``` 12 | $ npm install -g bash-handbook 13 | ``` 14 | 15 | Você será capaz de executar `bash-handbook` na sua linha de comando. Isso irá abrir o manual no `$PAGER` que você configurou. De outra forma, você pode continuar lendo aqui. 16 | 17 | O código fonte está disponível aqui: 18 | 19 | # Índice 20 | 21 | - [Introdução](#introdução) 22 | - [Estilos do shell](#estilos-do-shell) 23 | - [Interativo](#modo-interativo) 24 | - [Não-interativo](#modo-não-interativo) 25 | - [Códigos de saída](#códigos-de-saída) 26 | - [Comentários](#comentários) 27 | - [Variáveis](#variáveis) 28 | - [Variáveis locais](#variáveis-locais) 29 | - [Variáveis de ambiente](#variáveis-de-ambiente) 30 | - [Parâmetros de posição](#parâmetros-de-posição) 31 | - [Expansões do shell](#expansões-do-shell) 32 | - [Expansões de suporte](#expansões-de-suporte) 33 | - [Substituição de comandos](#substituição-de-comandos) 34 | - [Expansões aritiméticas](#expansões-aritiméticas) 35 | - [Aspas simples e duplas](#aspas-simples-e-duplas) 36 | - [Arrays](#arrays) 37 | - [Declarando array](#declarando-array) 38 | - [Expansões de Array](#expansões-de-array) 39 | - [Separando Array](#separando-array) 40 | - [Adicionando elementos no Array](#adicionando-elementos-no-array) 41 | - [Deletando elementos de um Array](#deletando-elementos-de-um-array) 42 | - [Streams, pipes e listas](#streams-pipes-e-listas) 43 | - [Streams](#streams) 44 | - [Pipes](#pipes) 45 | - [Lista de comandos](#lista-de-comando) 46 | - [Operadores condicionais](#operadores-condicionais) 47 | - [Expressões primárias e combinação de expressões](#expressões-primárias-e-combinação-de-expressões) 48 | - [Usando a condicional `if`](#usando-a-condicional-if) 49 | - [Usando a condicional `case`](#usando-a-condicional-case) 50 | - [Loops](#loops) 51 | - [`for` loop](#for-loop) 52 | - [`while` loop](#while-loop) 53 | - [`until` loop](#until-loop) 54 | - [`select` loop](#select-loop) 55 | - [Controlando o loop](#controlando-o-loop) 56 | - [Funções](#funções) 57 | - [Depurando](#depurando) 58 | - [Posfácio](#posfácio) 59 | - [Licença](#license) 60 | 61 | # Introdução 62 | 63 | Se você é um desenvolvedor, então você sabe o valor que o tempo tem. Otimizar seu processo de trabalho é um dos mais importantes aspectos do seu dia-a-dia. 64 | 65 | E, se entrarmos no caminho em direção à eficiência e produtividade, sempre esbarramos em ações que serão repetidas uma vez ou outra, como: 66 | 67 | * tirar um *screenshot* e fazer o upload para um servidor 68 | * processar texto em vários formatos 69 | * converter arquivos entre diferentes formatos 70 | * analisar o resultado da execução de um programa 71 | 72 | Entra em cena, o **Bash**, nosso salvador. 73 | 74 | Bash é um shell Unix escrito por [Brian Fox][] no formato de software livre para o projeto GNU, com a intenção de substituir o [Bourne shell](https://en.wikipedia.org/wiki/Bourne_shell). Ele foi lançado em 1989 e tem sido distribuído como shell padrão no Linux e OS X a um longo tempo. 75 | 76 | [Brian Fox]: https://en.wikipedia.org/wiki/Brian_Fox_(computer_programmer) 77 | 78 | 79 | E porque nós precisamos aprender algo que foi escrito a mais de 30 anos? A resposta é simples: essa _coisa_, hoje em dia, é uma das mais poderosas e portáveis ferramentas para escrever scripts para todos os sitemas baseados em Unix. E isso é a razão pela qual você deve aprender bash. Ponto. 80 | 81 | Nesse manual, eu vou descrever os conceitos mais importantes do bash através de exemplos. Eu espero que seja útil para você e que você possa aprender algo através deles. 82 | 83 | # Estilos do shell 84 | 85 | O usuário do shell bash pode trabalhar em dois modos - interativo e não-interativo. 86 | 87 | ## Modo Interativo 88 | 89 | Se você estiver trabalhando no Ubuntu, você tem sete terminais virtuais disponíveis para você. O ambiente de trabalho se posiciona no sétimo terminal virtual. Você pode voltar para uma GUI mais amigável usando o atalho `Ctrl-Alt-F7`. 90 | 91 | Você pode abrir o shell usando o atalho `Ctrl-Alt-F1`. Depois disso, a GUI que você acostuma utilizar irá desaparecer e um dos terminais virtuais será mostrado. 92 | 93 | Se você ver algo parecido com isso, então, você está trabalhando no modo interativo: 94 | 95 | user@host:~$ 96 | 97 | Aqui você pode digitar uma variedade de comandos Unix, como `ls`, `grep`, `cd`, `mkdir`, `rm` e ver o resultado das suas execuções. 98 | 99 | Chamamos isso de shell interativo porque ele interage diretamente com o usuário. 100 | 101 | Usar um terminal virtual, nem sempre é conveniente. Por exemplo, se você quiser editar um documento e executar um comando ao mesmo tempo, é melhor você usar um emulador de terminais virtuais, como: 102 | 103 | - [GNOME Terminal](https://en.wikipedia.org/wiki/GNOME_Terminal) 104 | - [Terminator](https://en.wikipedia.org/wiki/Terminator_(terminal_emulator)) 105 | - [iTerm2](https://en.wikipedia.org/wiki/ITerm2) 106 | - [ConEmu](https://en.wikipedia.org/wiki/ConEmu) 107 | 108 | ## Modo não-interativo 109 | 110 | No modo não-interativo, o shell recebe comandos de um arquivo ou um _pipe_ e executa eles. Quando o interpretador chega no final do arquivo, a sessão de processamento do shell é terminada e o processo anterior é retornado. 111 | 112 | Use os seguintes comandos para executar o shell em modo não-interativo: 113 | 114 | . /path/to/script.sh 115 | bash /path/to/script.sh 116 | 117 | No exemplo acima, `script.sh` é apenas um arquivo de texto comum, contendo comandos, que o interpretador shell pode executar. `sh` ou `bash` são interpretadores utilizados pelo shell. Você pode criar um `script.sh` usando seu editor de texto preferido (e.g. vim, nano, Sublime Text, Atom, etc). 118 | 119 | Você também pode simplificar a invocação do script transformando o arquivo em um executável usando o comando `chmod`: 120 | 121 | chmod +x /path/to/script.sh 122 | 123 | Além disso, a primeira linha do script deve indicar qual programa deve ser usado para executar o arquivo, como: 124 | 125 | ```bash 126 | #!/bin/bash 127 | echo "Hello, world!" 128 | ``` 129 | 130 | Ou, se você preferir usar `sh` ao invés do `bash`, mude `#!/bin/bash` para `#!/bin/sh`. Essa sequência de carácteres `#!`, é conhecida como [shebang](http://en.wikipedia.org/wiki/Shebang_%28Unix%29). Agora você pode executar scripts da seguinte maneira: 131 | 132 | /path/to/script.sh 133 | 134 | Um truque útil que usamos acima, é usar o comando `echo` para imprimir o texto na tela do terminal. 135 | 136 | Uma outra maneira de usar o _shebang_ é: 137 | 138 | ```bash 139 | #!/usr/bin/env bash 140 | echo "Hello, world!" 141 | ``` 142 | 143 | A vantagem desse modo de uso do _shebang_ é que ele irá utilizar o programa (nesse caso o `bash`) baseado no caminho `PATH` do seu ambiente. Esse modo é, muitas vezes, preferido, ao invés de usar o primeiro método mostrado acima, onde a localização do programa no seu ambiente, pode não ser a mesmo. Isso também é útil se a variável `PATH`, em um sistema, estiver configurada para uma versão diferente do programa. Um exemplo, seria a instalação de uma nova versão do `bash`, enquanto preservamos a versão original e inserimos a localização da nova versão na variável `PATH` do sistema. O uso do `#!/bin/bash` pode resultar no uso da versão original do `bash`, enquanto, `#!/usr/bin/env bash`, fará uso da nova versão. 144 | 145 | ## Códigos de saída 146 | 147 | Todo comando retorna um **código de sáida** (**retornando o estado** ou o **estado de saída**). Um comando executado com sucesso, sempre retorna `0` (código-zero), e um comando executado com falha, sempre retorna um valor não-zero (código de erro). Códigos de falhas devem conter um número inteiro positivo entre 1 e 255. 148 | 149 | Outro comando útil que nós podemos usar quando escrevemos scripts é o `exit`. Esse omando é usado para finalizar a execução atual e retornar um código de saída para o shell. Executando o `exit`, sem nenhum argumento, irá terminar o script que está em processamento e retornar o código de saída do último comando executado antes do `exit`. 150 | 151 | Quando um programa é finalizado, o shell atribui ao seu **código de saída** há variável `$?`. A variável `$?`, é o que normalmente usamos para testar se um script foi executado com sucesso ou não. 152 | 153 | Do mesmo modo que podemos usar `exit` para terminar um script, nós podemos usar o comando `return` para sair de uma função e retornar o **código de saída** para quem invocou essa função. Você também pode usar `exit` dentro de uma função, isso irá resultar na saída da função _e_ na finalização do programa. 154 | 155 | # Comentários 156 | 157 | Scripts podem conter _comentários_. Comentários são declarações especiais ignoradas pelo interpretador do `shell`. O início de um comentário deve conter o símbolo `#` e continuar até o final da linha. 158 | 159 | Por exemplo: 160 | 161 | ```bash 162 | #!/bin/bash 163 | # Esse script irá imprimir seu nome de usuário. 164 | whoami 165 | ``` 166 | 167 | > **Dica**: Use comentários para explicar o que seu script faz e _porque_. 168 | 169 | # Variáveis 170 | 171 | Como na maioria das linguages de programação, você pode criar variáveis no bash. 172 | 173 | Bash não conhece nenhum tipo de dados. Variáveis podem conter apenas números ou _strings_. Existem três tipos de variáveis que você pode criar: variáveis locais, variáveis de ambiente e variáveis de _parâmetros posicionados_. 174 | 175 | ## Variáveis locais 176 | 177 | **Variáveis locais** são variáveis que existem apenas no contido script. Elas são inacessíveis para outros programas ou scripts. 178 | 179 | Uma variável local pode ser declarada usando o sinal `=` (como regra, **não deve** conter nenhum espaço entre o nome da variável, `=` e o seu valor) e seu valor pode ser acessado usando o sinal `$`. Por exemplo: 180 | 181 | ```bash 182 | username="oieduardorabelo" # declarando a variável 183 | echo $username # imprimindo seu valor 184 | unset username # deletando a variável 185 | ``` 186 | 187 | Nós podemos declarar uma variável local para uma única função usando a declaração `local`. Com isso, a variável será automaticamente deletada quando a função terminar de ser executada. 188 | 189 | ```bash 190 | local local_var="Sou uma variável local" 191 | ``` 192 | 193 | ## Variáveis de ambiente 194 | 195 | **Variáveis de ambiente** são variáveis que podem ser acessadas por qualquer programa ou script sendo executado na sessão atual do shell. Elas são criadas como variáveis locais, mas usando a declaração `export` no início delas. 196 | 197 | ```bash 198 | export GLOBAL_VAR="Sou uma variável global" 199 | ``` 200 | 201 | Existem _muitas_ variáveis globais no bash. Você vai conhecer elas no decorrer do seu dia-a-dia, mas aqui você encontra uma tabela com as mais utilizadas: 202 | 203 | | Variáveis | Descrição | 204 | | :----------- | :------------------------------------------------------------ | 205 | | `$HOME` | O diretório inicial do usuário atual. | 206 | | `$PATH` | Uma lista separada por dois pontos `[:]` dos diretários que o shell irá procurar por comandos. | 207 | | `$PWD` | O diretório atual. | 208 | | `$RANDOM` | Número inteiro randômico entre 0 e 32767. | 209 | | `$UID` | Versão numérica do ID do usuário atual. | 210 | | `$PS1` | Sequência primária do seu prompt de comando. | 211 | | `$PS2` | Sequência secundária do seu prompt de comando. | 212 | 213 | Entre [nesse link](http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html#sect_03_02_04) para ver uma lista extendida de variáveis de ambiente do Bash. 214 | 215 | ## Parâmetros de posição 216 | 217 | **Parâmetros de posição** são variáveis alocadas aos parâmetros de uma função quando ela é executada. A seguinte tabela mostra os parâmetros de posição e outras variáveis especiais e quais os seus significados dentro da função. 218 | 219 | | Parâmetro | Descrição | 220 | | :------------- | :---------------------------------------------------------- | 221 | | `$0` | Nome do script. | 222 | | `$1 … $9` | Os parâmetros passados de 1 há 9. | 223 | | `${10} … ${N}` | Os parâmetros passados de 10 há N. | 224 | | `$*` or `$@` | Todos os parâmetros passados, exceto `$0`. | 225 | | `$#` | A soma da quantidade de parâmetros foi passada, não contando `$0`. | 226 | | `$FUNCNAME` | O nome da função (retornada como valor, apenas dentro da função). | 227 | 228 | No exemplo abaixo, os parâmetros posicinais serão `$0='./script.sh'`, `$1='foo'` e `$2='bar'`: 229 | 230 | ./script.sh foo bar 231 | 232 | Variáveis também podem ter um valor _padrão_. Nós podemos definir isso usando a sintaxe: 233 | 234 | ```bash 235 | # se a variável estiver vazia, atribua o valor padrão 236 | : ${VAR:='default'} 237 | : ${$1:='first'} 238 | # ou 239 | FOO=${FOO:-'default'} 240 | ``` 241 | 242 | # Expansões do shell 243 | 244 | _Expansões_ são realizadas na linha de comando após ela ser separada em _símbolos_. Em outras palavras, expansões são mecânismos para calcular operações aritméticas, salvar resultados de execuções de comandos e assim por diante. 245 | 246 | Se você estiver interessado, você pode ler [mais sobre expansões do shell](https://www.gnu.org/software/bash/manual/bash.html#Shell-Expansions). 247 | 248 | ## Expansões de suporte 249 | 250 | Expansões de suporte nos permite criar _strings_ arbitrárias. É parecido com _expansão de nomes de arquivos_. Por exemplo: 251 | 252 | ```bash 253 | echo bat{i,a,u}ta # batita batata batuta 254 | ``` 255 | 256 | Expansões também podem ser usadas para criar extensões numéricas, que podem ser iterados em um _loop_. 257 | 258 | ```bash 259 | echo {0..5} # 0 1 2 3 4 5 260 | echo {00..8..2} # 00 02 04 06 08 261 | ``` 262 | 263 | ## Substituição de comandos 264 | 265 | Substituição de comandos nos permite avaliar um comando e substituir seus valores em outro comando ou atribuição de variável. Substituição de comandos é realizado quando um comando é anexado por ``` `` ``` ou `$()`. Por exemplo, podemos usar isso da seguinte maneira: 266 | 267 | ```bash 268 | now=`date +%T` # horário atual 269 | # ou 270 | now=$(date +%T) # horário atual 271 | 272 | echo $now # 19:08:26 273 | ``` 274 | 275 | ## Expansões aritiméticas 276 | 277 | No bash, somos livres para fazer qualquer operação aritmética. Mas, expressões devem ser anexadas por `$(( ))`. O formato da operação aritmética é: 278 | 279 | ```bash 280 | result=$(( ((10 + 5*3) - 7) / 2 )) 281 | echo $result # 9 282 | ``` 283 | 284 | Dentro de expressões aritméticas, variáveis geralmente deverm ser usadas sem o prefixo `$`: 285 | 286 | ```bash 287 | x=4 288 | y=7 289 | echo $(( x + y )) # 11 290 | echo $(( ++x + y++ )) # 12 291 | echo $(( x + y )) # 13 292 | ``` 293 | 294 | ## Aspas simples e duplas 295 | 296 | Existe uma importante diferença entre aspas simples e duplas. Dentro das aspas duplas, variáveis ou comandos podem ser expandidos. Dentro de aspas simples não. Por exemplo: 297 | 298 | ```bash 299 | echo "Seu diretório inicial: $HOME" # Seu diretório inicial: /Users/ 300 | echo 'Seu diretório inicial: $HOME' # Seu diretório inicial: $HOME 301 | ``` 302 | 303 | Tome cuidado ao expandir variáveis locais ou de ambiente dentro de aspas se eles contiverem espaços em branco. Um exemplo disso, considere o uso do `echo` para imprimir algo: 304 | 305 | ```bash 306 | INPUT="Uma frase com estranhos espaços em branco." 307 | echo $INPUT # Uma frase com estranhos espaços em branco. 308 | echo "$INPUT" # Uma frase com estranhos espaços em branco. 309 | ``` 310 | 311 | O primeiro `echo` será invocado com 7 argumentos separados - $INPUT é separado em cada palavra, `echo` imprimi um único espaço em branco entre cada palavra. No segundo caso, `echo` é invocado com um único argumento (todo o valor do $INPUT, includingo seus espaços em branco). 312 | 313 | Agora, considere um exemplo mais sério: 314 | 315 | ```bash 316 | FILE="Minhas coisas favoritas.txt" 317 | cat $FILE # tentará imprimir 3 arquivos: `Minhas`, `coisas` e `favoritas.txt` 318 | cat "$FILE" # imprimi 1 arquivo: `Minhas coisas favoritas.txt` 319 | ``` 320 | 321 | Enquanto o problema desse exemplo pode ser resolvido apenas renomeando FILE para `Minhas-coisas-favoritas.txt`, considere a entrada do nome vindo de uma variável de ambiente, um parâmetro posicional ou o resultado de outro comando (`find`, `cat`, etc). Se a entrada *puder* conter espaços em branco, tome o cuidado de envolver a expansão em aspas. 322 | 323 | # Arrays 324 | 325 | Como em qualquer outra linguagem de programação, um array no bash é uma variável que permite o armazenamento de múltiplos valores. No bash, arrays também são de base zero, ou seja, o primeiro elemento do array tem o íncide 0. 326 | 327 | Ao lidar com arrays, nós devemos tomar um cuidado especial com as variáveis de ambiente `IFS`. **IFS**, que significa **Input Field Separator**, em português, algo como, **Separador dos campos de entrada**, são os carácteres que separam os elementos dentro de um array. O valor padrão desses campos é um espaço em braco, `IFS=' '`. 328 | 329 | ## Declarando array 330 | 331 | Para criar um array no bash, você pode simplesmente atribuir o valor ao index da variável do array: 332 | 333 | ```bash 334 | frutas[0]=Maça 335 | frutas[1]=Pera 336 | frutas[2]=Banana 337 | ``` 338 | 339 | As variáveis de arrays também podem ser criadas a partir de uma atribuição composta, como: 340 | 341 | ```bash 342 | frutas=(Maça Pera Banana) 343 | ``` 344 | 345 | ## Expansões de Array 346 | 347 | Elementos individuais do array, são igualmente expansíveis como qualquer outra variável: 348 | 349 | ```bash 350 | echo ${frutas[1]} # Pera 351 | ``` 352 | 353 | Todo o array pode ser expansível usando `*` ou `@` no lugar do índice numérico: 354 | 355 | ```bash 356 | echo ${frutas[*]} # Maça Pera Banana 357 | echo ${frutas[@]} # Maça Pera Banana 358 | ``` 359 | 360 | Tem uma importante (e súbita) diferença entre as duas linhas acima: considere que um elemento do array tenha espaços em branco: 361 | 362 | ```bash 363 | fruta[0]=Maça 364 | fruta[1]="Mamão papaia" 365 | fruta[2]=Banana 366 | ``` 367 | 368 | Nós queremos imprimir cada elemento do array separadamente em uma nova linha, então, vamos tentar usar a função nativa `printf`: 369 | 370 | ```bash 371 | printf "+ %s\n" ${frutas[*]} 372 | # + Maça 373 | # + Mamão 374 | # + papaia 375 | # + Banana 376 | ``` 377 | 378 | Porque o `Mamão` e `papaia` foram imprimidos em linhas separadas? Vamos tentar usando aspas: 379 | 380 | ```bash 381 | printf "+ %s\n" "${frutas[*]}" 382 | # + Maça Mamão papaia Banana 383 | ``` 384 | 385 | Agora, está tudo em uma linha só - isso não exatamente o que queremos! É aí que `${frutas[@]}` entra no jogo: 386 | 387 | ```bash 388 | printf "+ %s\n" "${frutas[@]}" 389 | # + Maça 390 | # + Mamão papaia 391 | # + Banana 392 | ``` 393 | 394 | Dentro das aspas duplas, `${frutas[@]}` é expandido separadamente para cada elemento do array, com seus espaços em branco preservados. 395 | 396 | ## Separando Array 397 | 398 | Além disso, você pode extrair um pedaço do array usando os operadores: 399 | 400 | ```bash 401 | echo ${frutas[@]:0:2} # Maça Mamão papaia 402 | ``` 403 | 404 | No exemplo acima, `${frutas[@]}` é expandido com todo o conteúdo do seu array, e `:0:2`, extraí o pedaço de tamanho 2, começando no índice 0. 405 | 406 | ## Adicionando elementos no Array 407 | 408 | Adicionar elementos no array é bem simples. Atribuições compostas são extremamente úteis nesse caso. Você pode fazer uso dessa maneira: 409 | 410 | ```bash 411 | frutas=(Laranja "${frutas[@]}" Melão Ameixa) 412 | echo ${frutas[@]} # Laranja Maça Mamão papaia Banana Melão Ameixa 413 | ``` 414 | 415 | No exemplo acima, `${frutas[@]}` é expandido com todo o conteúdo do seu array e é atribuido ao novo valor dentro do array `frutas`, sendo assim, mutando seu valor original. 416 | 417 | ## Deletando elementos de um Array 418 | 419 | Para deletar um elemento de um array, use o comando `unset`: 420 | 421 | ```bash 422 | unset frutas[0] # Deleta o item Laranja 423 | echo ${frutas[@]} # Maça Mamão papaia Banana Melão Ameixa 424 | ``` 425 | 426 | # Streams, pipes e listas 427 | 428 | Bash tem uma poderosa ferramente para trabalhar com outros programas e seus resultados. Usando _streams_ nós podemos enviar o resultado de um programa para outro programa ou arquivo, e assim, gravar logs ou fazer qualquer coisa que quisermos. 429 | 430 | _Pipes_ te dá a oportunidade de transportar e controlar a execução de comandos. 431 | 432 | É fundamental o entendimento de como usar essa poderosa e sofisticada ferramenta do Bash. 433 | 434 | ## Streams 435 | 436 | Ao executar qualquer comando no Bash, ele recebe esses dados como parâmetros e envia uma sequência ou _streams_ de caracteres. Esses _streams_ podem ser redirecionados em arquivos ou em outro _stream_. 437 | 438 | Existem três tipos de saídas de dados, conhecidos como _descritores_: 439 | 440 | | Código | Descritor | Descrição | 441 | | :--: | :--------: | :------------------- | 442 | | `0` | `stdin` | O padrão de entrada de dados. | 443 | | `1` | `stdout` | O padrão de saída de dados. | 444 | | `2` | `stderr` | O padrão de saída de erros. | 445 | 446 | Redirecionamento torna possível o controle de onde a saída do comando vai parar, e, de onde a entrada de dados veem. Para redirecionar _streams_, você pode usar esses operadores: 447 | 448 | | Operadores | Descrição | 449 | | :------: | :------------------------------------------- | 450 | | `>` | Redireciona a saída de dados | 451 | | `&>` | Redireciona a saída de dados e de erros | 452 | | `&>>` | Anexa o redirecionamento de saída e erros | 453 | | `<` | Redireciona a entrada de dados | 454 | | `<<` | Sintaxe do comando ["Here documents"](http://tldp.org/LDP/abs/html/here-docs.html) | 455 | | `<<<` | Sintaxe do comando ["Here strings"](http://www.tldp.org/LDP/abs/html/x17837.html) | 456 | 457 | Veja aqui alguns exemplos de redirecionamento: 458 | 459 | ```bash 460 | # a saída do comando `ls` será escrita no arquivo lista.txt 461 | ls -l > lista.txt 462 | 463 | # adiciona a saída do comando no final do arquivo lista.txt 464 | ls -a >> lista.txt 465 | 466 | # todos os erros serão escritos no arquivo erros.txt 467 | grep da * 2> erros.txt 468 | 469 | # lê o arquivo erros.txt 470 | less < errors.txt 471 | ``` 472 | 473 | ## Pipes 474 | 475 | Nós podemos redirecionar os _streams_ padrões não apenas para arquivos, mas também, para outros programas. **Pipes** nos permite usar a saída de um programa, como entrada de outro. 476 | 477 | No exemplo abaixo, `comando1` envia sua saída para `comando2`, que então passa sua saída como entrada para `comando3`: 478 | 479 | comando1 | comando2 | comando3 480 | 481 | Construções como essa, são chamadas de **pipelines**. 482 | 483 | Na prática, isso pode ser usado para processar dados através de vários programas. Por exemplo, no exemplo a seguir, a saída do `ls -l` é enviada para o comando `grep`, que então imprimi apenas os arquivos que tenham a extensão `.md`, e sua saída, é finalmente enviada para o comando `less`: 484 | 485 | ls -l | grep .md$ | less 486 | 487 | ## Lista de comandos 488 | 489 | Uma **lista de comandos** é uma sequência de um ou mais _pipelines_ separados pelos operadores `;`, `&`, `&&` ou `||`. 490 | 491 | Se um comando termina com um operador `&`, o shell executará o comando asíncronamente através de um _subshell_. Em outras palavras, esse comando será executado em segundo plano (ou _background_). 492 | 493 | Comandos separados por `;` serão executados em sequência: um após o outro. O shell esperada a finalização de cada comando para executar o próximo. 494 | 495 | ```bash 496 | # comando1 será executado após a finalização do comando1 497 | comando1 ; comando2 498 | 499 | # que é o mesmo que 500 | command1 501 | command2 502 | ``` 503 | 504 | Uma lista separada por `&&` e `||` são conhecidos também como listas _AND_ e _OR_, 505 | 506 | Uma lista _AND_ é parecida com isso: 507 | 508 | ```bash 509 | # comando2 será executado se, e apenas se, o comando1 finalize seu processo com sucesso (retorando um estado de saída 0) 510 | comando1 && comando2 511 | ``` 512 | 513 | Uma lista _OR_ é parecida com isso: 514 | 515 | ```bash 516 | # comando2 será executado se, e apenas se, o comando1 não finalize seu processo com sucesso (retornando um estado de saída não-zero) 517 | comando1 || comando2 518 | ``` 519 | 520 | O código retornado pelas listas _AND_ ou _OR_, são o estado do último comando executado. 521 | 522 | # Operadores condicionais 523 | 524 | Como em qualquer outra linguagem, as condicionais no Bash nos permitem decidir qual ação realizar. O resultado é determinado pela análise da expressão, que deverá ser conter `[[ ]]` em volta dela. 525 | 526 | Expressões condicionais podem conter os operadores `&&` e `||`, como vimos, _AND_ e _OR_. Além disso, existem [várias outras expressões](#expressões-primárias-e-combinação-de-expressões) que podem ser utilizadas. 527 | 528 | Existem duas condicionais diferentes: a condicional `if`, e a condicional `case`. 529 | 530 | ## Expressões primárias e combinação de expressões 531 | 532 | Expressões dentro do `[[ ]]` (ou `[ ]` para `sh`), são chamados de **comandos de teste** ou **primários**. Essas expressões ajudam a indicar o resultado de uma operação condicional. Nas tabelas abaixos, estamos usando `[ ]`, porque ele também funciona para `sh`. Para saber mais, [veja aqui a diferença entre aspas simples e aspas duplas dentro dos colchetes no Bash.](http://serverfault.com/a/52050). 533 | 534 | **Trabalhando com o sistema de arquivos:** 535 | 536 | | Primários | Quer dizer | 537 | | :-----------: | :----------------------------------------------------------- | 538 | | `[ -e FILE ]` | _true_ se `FILE` existir, do inglês _**e**xists_. | 539 | | `[ -f FILE ]` | _true_ se `FILE` existir e for um arquivo normal, do inglês _**f**ile_. | 540 | | `[ -d FILE ]` | _true_ se `FILE` existir e for executável, do inglês _**d**irectory_. | 541 | | `[ -s FILE ]` | _true_ se `FILE` existir e não for vazio, seu tamanho é maior que 0, do inglês _**s**ize_. | 542 | | `[ -r FILE ]` | _true_ se `FILE` existir e for possível a leitura, do inglês _**r**eadable_. | 543 | | `[ -w FILE ]` | _true_ se `FILE` existir e for possível a escrita, do inglês _**w**ritable_. | 544 | | `[ -x FILE ]` | _true_ se `FILE` existir e for possíve executá-lo, do inglês _e**x**ecutable_. | 545 | | `[ -L FILE ]` | _true_ se `FILE` existir e for um link simbólico, do inglês _symbolic **l**ink_. | 546 | | `[ FILE1 -nt FILE2 ]` | FILE1 é mais novo que FILE2, do inglês _**n**ewer **t**han_. | 547 | | `[ FILE1 -ot FILE2 ]` | FILE1 é mais velho que FILE2, do inglês _**o**lder **t**han_. | 548 | 549 | **Trabalhando com _strings_:** 550 | 551 | | Primários | Quer dizer | 552 | | :------------: | :---------------------------------------------------------- | 553 | | `[ -z STR ]` | `STR` é vazio, seu tamanho é zero, do inglês _**z**ero_. | 554 | | `[ -n STR ]` |`STR` não é vazio, seu tamanho não é zero, do inglês _**n**on-zero_. | 555 | | `[ STR1 == STR2 ]` | `STR1` e `STR2` são iguais. | 556 | | `[ STR1 != STR2 ]` | `STR1` e `STR2` não são iguais. | 557 | 558 | **Operadores aritiméticos binários:** 559 | 560 | | Primários | Quer dizer | 561 | | :-----------------: | :----------------------------------------------------- | 562 | | `[ ARG1 -eq ARG2 ]` | `ARG1` é igual ao `ARG2`, do inglês _**eq**ual_. | 563 | | `[ ARG1 -ne ARG2 ]` | `ARG1` não é igual ao `ARG2`, do inglês _**n**ot **e**qual_. | 564 | | `[ ARG1 -lt ARG2 ]` | `ARG1` é menor que `ARG2`, do inglês _**l**ess **t**han_. | 565 | | `[ ARG1 -le ARG2 ]` | `ARG1` é menor ou igual que `ARG2`, do inglês _**l**ess than or **e**qual_. | 566 | | `[ ARG1 -gt ARG2 ]` | `ARG1` é maior que `ARG2`, do inglês _**g**reater **t**han_. | 567 | | `[ ARG1 -ge ARG2 ]` | `ARG1` é maior ou igual que `ARG2` _**g**reater than or **e**qual_. | 568 | 569 | Condicionais podem ser combinadas usando as **expressões de combinação:** 570 | 571 | | Expressão | Efeito | 572 | | :------------: | :---------------------------------------------------------- | 573 | | `[ ! EXPR ]` | _true_ se `EXPR` é falso. | 574 | | `[ (EXPR) ]` | Retorna o valor da `EXPR`. | 575 | | `[ EXPR1 -a EXPR2 ]` | Operador lógico _AND_. _true_ se `EXPR1` e `EXPR2` são verdadeiros, do inglês _**a**nd_. | 576 | | `[ EXPR1 -o EXPR2 ]` | Operador lógico _OR_. _true_ se `EXPR1` ou `EXPR2` são verdadeiros, do inglês _**o**r_. | 577 | 578 | Com certeza existem muitos outros comandos e expressões úteis para seu caso, você fácilmente encontra-los na [página de manual do Bash](http://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html). 579 | 580 | ## Usando a condicional `if` 581 | 582 | Declarações `if` funcionam da mesma maneira como em outras linguagens de programação. Se a expressão dentro dos colchetes for verdadeira, o codógio dentro do bloco `then` e até o `fi` será executado. `fi` indica o final de uma condicional a ser executada. 583 | 584 | ```bash 585 | # única linha 586 | if [[ 1 -eq 1 ]]; then echo "true"; fi 587 | 588 | # múltipla linha 589 | if [[ 1 -eq 1 ]]; then 590 | echo "true" 591 | fi 592 | ``` 593 | 594 | Da mesma forma, podemos usar uma declaração `if..else`, como: 595 | 596 | ```bash 597 | # única linha 598 | if [[ 2 -ne 1 ]]; then echo "true"; else echo "false"; fi 599 | 600 | # múltipla linha 601 | if [[ 2 -ne 1 ]]; then 602 | echo "true" 603 | else 604 | echo "false" 605 | fi 606 | ``` 607 | 608 | As vezes, condicionais `if..else` não são suficientes para o que queremos fazer. Nesse caso, não devemos esquecer da existência da condicional `if..elif..else`, que sempre vêm a calhar. 609 | 610 | Veja o exemplo abaixo: 611 | 612 | ```bash 613 | if [[ `uname` == "Adão" ]]; then 614 | echo "Não coma a maça!" 615 | elif [[ `uname` == "Eva" ]]; then 616 | echo "Não pegue a maça!" 617 | else 618 | echo "Maças são deliciosas!" 619 | fi 620 | ``` 621 | 622 | ## Usando a condicional `case` 623 | 624 | Se você estiver analisando várias possibilidades diferentes para ter ações diferentes, usar a condicional `case` pode ser mais útil do que várias condicionais `if` aninhadas. Veja abaixo um exemplo complexo de usando a condicional `case`: 625 | 626 | ```bash 627 | case "$ext" in 628 | "jpg"|"jpeg") 629 | echo "É uma imagem com extensão jpg" 630 | ;; 631 | "png") 632 | echo "É uma imagem com extensão png" 633 | ;; 634 | "gif") 635 | echo "É uma imagem com extensão gif" 636 | ;; 637 | *) 638 | echo "Oops! Não é uma imagem!" 639 | ;; 640 | esac 641 | ``` 642 | 643 | A condicional `case` verifica a expressão que corresponde a um padrão. O sinal `|` é usado para separar múltiplos padrões e o operador `)` finaliza a lista de padrões. A expressão `*` é o padrão para todo o restante que não corresponder a nenhum item das suas listas. Cada bloco de comandos deve ser separado pelo operador `;;`. 644 | 645 | # Loops 646 | 647 | Aqui não teremos nenhuma surpresa. Assim como qualquer linguagem de programação, um loop no bash é um bloco de código que se repete enquanto a condição em controle for verdadeira. 648 | 649 | Existem quatro tipos de loops no Bash: `for`, `while`, `until` e `select`. 650 | 651 | ## `for` loop 652 | 653 | O `for` é bem similar ao seu irmão em C. Ele se parece com: 654 | 655 | ```bash 656 | for arg in elem1 elem2 ... elemN 657 | do 658 | # código 659 | done 660 | ``` 661 | 662 | Durante cada etapa do loop, `arg` assume os valores de `elem1` até `elemN`. Valores também podem ser espaços reservados ou [expansões de suporte](#expansões-de-suporte). 663 | 664 | E também podemos escrever o loop `for` em apenas uma linha, mas nesse caso, é preciso colocar um ponto e vírgula antes do `do`, como no exemplo: 665 | 666 | ```bash 667 | for i in {1..5}; do echo $i; done 668 | ``` 669 | 670 | A propósito, se `for..in..do` parece um pouco estranho para você, você também pode escrever o `for` em estilo C, como a seguir: 671 | 672 | ```bash 673 | for (( i = 0; i < 10; i++ )); do 674 | echo $i 675 | done 676 | ``` 677 | 678 | `for` é útil quando nós queremos fazer a mesma operação em cada arquivo em um diretório. Por exemplo, se precisamos mover todos os arquivos `.bash` dentro da pasta `script` e dar aos arquivos permissões de execução, nosso script será parecido com isso: 679 | 680 | ```bash 681 | #!/bin/bash 682 | 683 | for FILE in $HOME/*.bash; do 684 | mv "$FILE" "${HOME}/scripts" 685 | chmod +x "${HOME}/scripts/${FILE}" 686 | done 687 | ``` 688 | 689 | ## `while` loop 690 | 691 | O loop `while` testa uma condição e executa a sequência de comandos desde que a condição seja verdadeira. A condição não é nada mais que uma [expressão primária](#expressões-primárias-e-combinação-de-expressões) usada também em `if..then`. Então, um loop `while` se parece com: 692 | 693 | ```bash 694 | while [[ condition ]] 695 | do 696 | # código 697 | done 698 | ``` 699 | 700 | Tal como no caso do loop `for`, se quisermos escrever uma condição `do` na mesma linha, temos que usar um ponto e vírgula antes. 701 | 702 | Um exemplo prático seria: 703 | 704 | ```bash 705 | #!/bin/bash 706 | 707 | # Retorna o quadrado dos números de 0 à 9 708 | x=0 709 | while [[ $x -lt 10 ]]; do # valor de x é menor que 10 710 | echo $(( x * x )) 711 | x=$(( x + 1 )) # aumenta o x 712 | done 713 | ``` 714 | 715 | ## `until` loop 716 | 717 | O loop `until` é exatamente o oposto do loop `while`. Assim como o `while`, ele recebe uma condição teste, mas, só continua executando enquanto a condição for falsa: 718 | 719 | ```bash 720 | until [[ cond ]]; do 721 | # código 722 | done 723 | ``` 724 | 725 | ## `select` loop 726 | 727 | O loop `select` nos ajuda a organizar um menu para o usuário. Ele tem quase a mesma sintaxe que o loop `for`: 728 | 729 | ```bash 730 | select respostas in elem1 elem2 ... elemN 731 | do 732 | # código 733 | done 734 | ``` 735 | 736 | O `select` imprimi todos os `elem1..elemN` na tela, junto de suas sequências numéricas, e depois disso, pergunta ao usuário. Normalmente, isso se parece com `$?` (a variável `PS3`). A resposta será salva em `respotas`. Se `respostas` for um número entre `1..N`, então o código será executado e `select` vai para a próxima iteração - isso porquê nós devemos usar a declaração `break`. 737 | 738 | Um exemplo prático se parece com esse: 739 | 740 | ```bash 741 | #!/bin/bash 742 | 743 | PS3="Escolha uma gerenciador de pacotes: " 744 | select ITEM in bower npm gem pip 745 | do 746 | echo -n "Digite o nome de um de pacote: " && read PACKAGE 747 | case $ITEM in 748 | bower) bower install $PACKAGE ;; 749 | npm) npm install $PACKAGE ;; 750 | gem) gem install $PACKAGE ;; 751 | pip) pip install $PACKAGE ;; 752 | esac 753 | break # evita loops infinitos 754 | done 755 | ``` 756 | 757 | Esse example pergunta ao usuário qual gerenciador de pacote ele deseja usar. E em seguida, quais pacotes gostaríamos de instalar e finalmente, executa o processo de instalação. 758 | 759 | Se rodarmos isso, teremos: 760 | 761 | ``` 762 | $ ./my_script 763 | 1) bower 764 | 2) npm 765 | 3) gem 766 | 4) pip 767 | Escolha uma gerenciador de pacotes: 2 768 | Digite o nome de um de pacote: bash-handbook 769 | 770 | ``` 771 | 772 | ## Controlando o loop 773 | 774 | Existem situações onde precisamos parar o loop antes da sua finalização normal ou pular uma iteração. Nesses casos, nós podemos usar as declarações `break` e `continue`, que são nativas do shell. Ambos funcionam com qualquer tipo de loop. 775 | There are situations when we need to stop a loop before its normal ending or step over an iteration. In these cases, we can use the shell built-in `break` and `continue` statements. Both of these work with every kind of loop. 776 | 777 | A declaração `break` é usada para sair do loop atual antes da sua finalização. Nós já o conhecemos. 778 | The `break` statement is used to exit the current loop before its ending. We have already met with it. 779 | 780 | A delcaração `continue` pula uma iteração. Podemos usa-la desse modo: 781 | The `continue` statement steps over one iteration. We can use it as such: 782 | 783 | ```bash 784 | for (( i = 0; i < 10; i++ )); do 785 | if [[ $(( i % 2 )) -eq 0 ]]; then continue; fi 786 | echo $i 787 | done 788 | ``` 789 | 790 | Se você rodar o exemplo acima, ele vai imprimir os números ímpares de 0 à 9. 791 | If we run the example above, it will print all odd numbers from 0 through 9. 792 | 793 | # Funções 794 | 795 | Em scripts, nós temos a habilidade de definir e chamar funções. Assim como em qualquer linguagem de programação, funções no bash são pedaços de códigos, mas elas são tratadas um pouquinho diferentes. 796 | 797 | No bash, funções são sequências de comandos agrupados sob um mesmo nome, e esse nome, é o nome da função. Chamar uma função é o mesmo que chamar qualquer outro programa, você escreve o nome da função e ela será invocada. 798 | 799 | Podemos declarar funções dessa maneira: 800 | 801 | ```bash 802 | my_func () { 803 | # código 804 | } 805 | 806 | my_func # invoca função 807 | ``` 808 | 809 | Devemos declarar a função antes de invoca-la. 810 | 811 | Funções podem receber argumentos e retornar um resultado - o código de saída. Argumentos, em funções, são tratados da mesma maneira que os argumentos dados ao script no [modo não-interativo](#modo-não-interativo) - usando os [parâmetros de posição](#parâmetros-de-posição). O resultado pode ser retornado usando o comando `return`. 812 | 813 | Abaixo é uma função que recebe um nome e retorna `0`, indicando que foi executado com sucesso. 814 | 815 | ```bash 816 | # function with params 817 | bemVindo () { 818 | if [[ -n $1 ]]; then 819 | echo "Bem-vindo, $1!" 820 | else 821 | echo "Bem-vindo, desconhecido!" 822 | fi 823 | return 0 824 | } 825 | 826 | bemVindo Eduardo # Hello, Eduardo! 827 | bemVindo # Hello, desconhecido! 828 | ``` 829 | 830 | Nós já falamos sobre [códigos de saída](#códigos-de-saída). O comando `return` sem argumentos retorna o código de saída do último comando executado. Acima, `return 0` vai retornar o código bem sucedido, `0`. 831 | 832 | ## Depurando 833 | 834 | O shell nós dá ferramentas para depurar nossos scripts. Se você quer rodar um script em modo de depuração, nós usamos um modo especial em nosso _shebang_: 835 | 836 | ```bash 837 | #!/bin/bash options 838 | ``` 839 | 840 | Esse `options` é a configuração que muda o comportamento do shell. A tabela abaixo mostra uma lista de opções que podem ser úteis para você: 841 | 842 | | Atalho | Nome | Descrição | 843 | | :---: | :---------- | :----------------------------------------------------- | 844 | | `-f` | noglob | Desativa expanção de nome de arquivos, em inglês, _globbing_. | 845 | | `-i` | interactive | Script roda no modo _interativo_. | 846 | | `-n` | noexec | Lê comandos, mas não os executa (verifica a sintaxe). | 847 | | `-t` | — | Saí da execução depois do primeiro comando. | 848 | | `-v` | verbose | Imprimi cada comando no `stderr` antes de executa-los. | 849 | | `-x` | xtrace | Imprimi cada comando e expande seus argumentos e envia para o `stderr` antes de executa-los. | 850 | 851 | Por exemplo, podemos ter scripts com `-x` como opção, assim como: 852 | 853 | ```bash 854 | #!/bin/bash -x 855 | 856 | for (( i = 0; i < 3; i++ )); do 857 | echo $i 858 | done 859 | ``` 860 | 861 | Isso vai imprimir o valor das variáveis para o `stdout` junto de outras informações úteis: 862 | 863 | ``` 864 | $ ./my_script 865 | + (( i = 0 )) 866 | + (( i < 3 )) 867 | + echo 0 868 | 0 869 | + (( i++ )) 870 | + (( i < 3 )) 871 | + echo 1 872 | 1 873 | + (( i++ )) 874 | + (( i < 3 )) 875 | + echo 2 876 | 2 877 | + (( i++ )) 878 | + (( i < 3 )) 879 | ``` 880 | 881 | As vezes nós precisamos depurar uma parte do script. Nesse caso, usar o comando `set` é mais conveniente. Esse comando habilita e desabilita opções. Opções são desabilitadas usando `-` e habilitadas usando `+`: 882 | 883 | ```bash 884 | #!/bin/bash 885 | 886 | echo "xtrace está desabilitado" 887 | set -x 888 | echo "xtrace está habilitado" 889 | set +x 890 | echo "xtrace foi desabilitado novamente" 891 | ``` 892 | 893 | # Posfácio 894 | 895 | Eu espero que esse pequeno guia tenha sido interação e tenha te ajudado a entender um pouco mais sobre o Bash. Para ser honesto, eu escrevi esse guia para mim mesmo, para assim, não esquecer o básico do bash. Eu tentei escrever de uma maneira concisa, mas significativamente útil e eu espero que você tenha gostado. 896 | 897 | Esse guia narra minha própria experiência com o Bash. Ele não tem foco de abranger toda as funcionalidades, e, se você quiser saber mais, pode começar através do `man bash`. 898 | 899 | Contribuições são absolutamente bem-vindas, e eu ficarei grato por qualquer correção ou perguntas que você vier a ter e me enviar. Para isso, crie uma [nova questão aqui](https://github.com/denysdovhan/bash-handbook/issues). 900 | 901 | Obrigado por ler esse guia de bolso! 902 | 903 | # Quer aprender mais? 904 | 905 | Aqui tem uma lista de outros materiais, todos em inglês, sobre o Bash: 906 | 907 | * Bash man page. In many environments that you can run Bash, the help system `man` can display information about Bash, by running the command `man bash`. For more information on the `man` command, see the web page ["The man Command"](http://www.linfo.org/man.html) hosted at [The Linux Information Project](http://www.linfo.org/). 908 | * ["Bourne-Again SHell manual"](https://www.gnu.org/software/bash/manual/) in many formats, including HTML, Info, TeX, PDF, and Texinfo. Hosted at . As of 2016/01, this covers version 4.3, last updated 2015/02/02. 909 | * [Bash 3.2 Man page](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/bash.1.html) hosted at Apple's Mac Developer Library site. As of 2016/01, this covers version 3.2, last updated 2006/09/28. 910 | 911 | 912 | # Licença 913 | 914 | [![CC 4.0][cc-image]][cc-url] © [Denys Dovhan](http://denysdovhan.com) 915 | 916 | [cc-url]: http://creativecommons.org/licenses/by/4.0/ 917 | [cc-image]: https://i.creativecommons.org/l/by/4.0/80x15.png 918 | -------------------------------------------------------------------------------- /translations/zh-CN/README.md: -------------------------------------------------------------------------------- 1 | # bash-handbook-zh-CN 2 | 3 | 谨以此文档献给那些想学习Bash又无需钻研太深的人。 4 | 5 | > **Tip**: 不妨尝试 [**learnyoubash**](https://git.io/learnyoubash) — 一个基于本文档的交互式学习平台! 6 | 7 | # 目录 8 | 9 | - [前言](#前言) 10 | - [Shells与模式](#shells与模式) 11 | - [交互模式](#交互模式) 12 | - [非交互模式](#非交互模式) 13 | - [返回值](#返回值) 14 | - [注释](#注释) 15 | - [变量](#变量) 16 | - [局部变量](#局部变量) 17 | - [环境变量](#环境变量) 18 | - [位置参数](#位置参数) 19 | - [Shell扩展](#shell扩展) 20 | - [大括号扩展](#大括号扩展) 21 | - [命令置换](#命令置换) 22 | - [算数扩展](#算数扩展) 23 | - [单引号和双引号](#单引号和双引号) 24 | - [数组](#数组) 25 | - [数组声明](#数组声明) 26 | - [数组扩展](#数组扩展) 27 | - [数组切片](#数组切片) 28 | - [向数组中添加元素](#向数组中添加元素) 29 | - [从数组中删除元素](#从数组中删除元素) 30 | - [流,管道以及序列](#流管道以及序列) 31 | - [流](#流) 32 | - [管道](#管道) 33 | - [命令序列](#命令序列) 34 | - [条件语句](#条件语句) 35 | - [基元和组合表达式](#基元和组合表达式) 36 | - [使用`if`](#使用if) 37 | - [使用`case`](#使用case) 38 | - [循环](#循环) 39 | - [`for`循环](#for循环) 40 | - [`while`循环](#while循环) 41 | - [`until`循环](#until循环) 42 | - [`select`循环](#select循环) 43 | - [循环控制](#循环控制) 44 | - [函数](#函数) 45 | - [Debugging](#debugging) 46 | - [后记](#后记) 47 | - [其它资源](#其它资源) 48 | - [License](#license) 49 | 50 | # 前言 51 | 52 | 如果你是一个程序员,时间的价值想必心中有数。持续优化工作流是你最重要的工作之一。 53 | 54 | 在通往高效和高生产力的路上,我们经常不得不做一些重复的劳动,比如: 55 | 56 | * 对屏幕截图,并把截图上传到服务器上 57 | * 处理各种各种的文本 58 | * 在不同格式之间转换文件 59 | * 格式化一个程序的输出 60 | 61 | 就让**Bash**来拯救我们吧。 62 | 63 | Bash是一个Unix Shell,作为[Bourne shell](https://en.wikipedia.org/wiki/Bourne_shell)的free software替代品,由[Brian Fox][]为GNU项目编写。它发布于1989年,在很长一段时间,Linux系统和OS X系统都把Bash作为默认的shell。 64 | 65 | [Brian Fox]: https://en.wikipedia.org/wiki/Brian_Fox_(computer_programmer) 66 | 67 | 68 | 那么,我们学习这个有着30多年历史的东西意义何在呢?答案很简单:这是当今最强大、可移植性最好的,为所有基于Unix的系统编写高效率脚本的工具之一。这就是你需要学习bash的原因。 69 | 70 | 在本文中,我会用例子来描述在bash中最重要的思想。希望这篇概略性的文章对你有帮助。 71 | 72 | # Shells与模式 73 | 74 | bash shell有交互和非交互两种模式。 75 | 76 | ## 交互模式 77 | 78 | Ubuntu用户都知道,在Ubuntu中有7个虚拟终端。 79 | 桌面环境接管了第7个虚拟终端,于是按下`Ctrl-Alt-F7`,可以进入一个操作友好的图形用户界面。 80 | 81 | 即便如此,还是可以通过`Ctrl-Alt-F1`,来打开shell。打开后,熟悉的图形用户界面消失了,一个虚拟终端展现出来。 82 | 83 | 看到形如下面的东西,说明shell处于交互模式下: 84 | 85 | user@host:~$ 86 | 87 | 接着,便可以输入一系列Unix命令,比如`ls`,`grep`,`cd`,`mkdir`,`rm`,来看它们的执行结果。 88 | 89 | 之所以把这种模式叫做交互式,是因为shell直接与用户交互。 90 | 91 | 直接使用虚拟终端其实并不是很方便。设想一下,当你想编辑一个文档,与此同时又想执行另一个命令,这种情况下,下面的虚拟终端模拟器可能更适合: 92 | 93 | - [GNOME Terminal](https://en.wikipedia.org/wiki/GNOME_Terminal) 94 | - [Terminator](https://en.wikipedia.org/wiki/Terminator_(terminal_emulator)) 95 | - [iTerm2](https://en.wikipedia.org/wiki/ITerm2) 96 | - [ConEmu](https://en.wikipedia.org/wiki/ConEmu) 97 | 98 | ## 非交互模式 99 | 100 | 在非交互模式下,shell从文件或者管道中读取命令并执行。当shell解释器执行完文件中的最后一个命令,shell进程终止,并回到父进程。 101 | 102 | 可以使用下面的命令让shell以非交互模式运行: 103 | 104 | sh /path/to/script.sh 105 | bash /path/to/script.sh 106 | 107 | 上面的例子中,`script.sh`是一个包含shell解释器可以识别并执行的命令的普通文本文件,`sh`和`bash`是shell解释器程序。你可以使用任何喜欢的编辑器创建`script.sh`(vim,nano,Sublime Text, Atom等等)。 108 | 109 | 除此之外,你还可以通过`chmod`命令给文件添加可执行的权限,来直接执行脚本文件: 110 | 111 | chmod +x /path/to/script.sh 112 | 113 | 这种方式要求脚本文件的第一行必须指明运行该脚本的程序,比如: 114 | 115 | ```bash 116 | #!/bin/bash 117 | echo "Hello, world!" 118 | ``` 119 | 如果你更喜欢用`sh`,把`#!/bin/bash`改成`#!/bin/sh`即可。`#!`被称作[shebang](https://zh.wikipedia.org/wiki/Shebang)。之后,就能这样来运行脚本了: 120 | 121 | /path/to/script.sh 122 | 123 | 上面的例子中,我们使用了一个很有用的命令`echo`来输出字符串到屏幕上。 124 | 125 | 我们还可以这样来使用shebang: 126 | 127 | ```bash 128 | #!/usr/bin/env bash 129 | echo "Hello, world!" 130 | ``` 131 | 132 | 这样做的好处是,系统会自动在`PATH`环境变量中查找你指定的程序(本例中的`bash`)。相比第一种写法,你应该尽量用这种写法,因为程序的路径是不确定的。这样写还有一个好处,操作系统的`PATH`变量有可能被配置为指向程序的另一个版本。比如,安装完新版本的`bash`,我们可能将其路径添加到`PATH`中,来“隐藏”老版本。如果直接用`#!/bin/bash`,那么系统会选择老版本的`bash`来执行脚本,如果用`#!/usr/bin/env bash`,则会使用新版本。 133 | 134 | 135 | ## 返回值 136 | 137 | 每个命令都有一个**返回值**(**返回状态**或者**退出状态**)。命令执行成功的返回值总是`0`(零值),执行失败的命令,返回一个非0值(错误码)。错误码必须是一个1到255之间的整数。 138 | 139 | 在编写脚本时,另一个很有用的命令是`exit`。这个命令被用来终止当前的执行,并把返回值交给shell。当`exit`不带任何参数时,它会终止当前脚本的执行并返回在它之前最后一个执行的命令的返回值。 140 | 141 | 一个程序运行结束后,shell将其**返回值**赋值给`$?`环境变量。因此`$?`变量通常被用来检测一个脚本执行成功与否。 142 | 143 | 与使用`exit`来结束一个脚本的执行类似,我们可以使用`return`命令来结束一个函数的执行并将**返回值**返回给调用者。当然,也可以在函数内部用`exit`,这 _不但_ 会中止函数的继续执行,_而且_ 会终止整个程序的执行。 144 | 145 | # 注释 146 | 147 | 脚本中可以包含 _注释_。注释是特殊的语句,会被`shell`解释器忽略。它们以`#`开头,到行尾结束。 148 | 149 | 一个例子: 150 | 151 | ```bash 152 | #!/bin/bash 153 | # This script will print your username. 154 | whoami 155 | ``` 156 | 157 | > **Tip**: 用注释来说明你的脚本是干什么的,以及 _为什么_ 这样写。 158 | 159 | # 变量 160 | 161 | 跟许多程序设计语言一样,你可以在bash中创建变量。 162 | 163 | Bash中没有数据类型。变量只能包含数字或者由一个或多个字符组成的字符串。你可以创建三种变量:局部变量,环境变量以及作为 _位置参数_ 的变量。 164 | 165 | ## 局部变量 166 | 167 | **局部变量** 是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。 168 | 169 | 局部变量可以用`=`声明(作为一种约定,变量名、`=`、变量的值之间 **不应该** 有空格),其值可以用`$`访问到。举个例子: 170 | 171 | ```bash 172 | username="denysdovhan" # 声明变量 173 | echo $username # 输出变量的值 174 | unset username # 删除变量 175 | ``` 176 | 177 | 我们可以用`local`关键字声明属于某个函数的局部变量。这样声明的变量会在函数结束时消失。 178 | 179 | ```bash 180 | local local_var="I'm a local value" 181 | ``` 182 | 183 | ## 环境变量 184 | 185 | **环境变量** 是对当前shell会话内所有的程序或脚本都可见的变量。创建它们跟创建局部变量类似,但使用的是`export`关键字。 186 | 187 | ```bash 188 | export GLOBAL_VAR="I'm a global variable" 189 | ``` 190 | 191 | bash中有 _非常多_ 的环境变量。你会非常频繁地遇到它们,这里有一张速查表,记录了在实践中最常见的环境变量。 192 | 193 | | Variable | Description | 194 | | :----------- | :------------------------------------------------------------ | 195 | | `$HOME` | 当前用户的用户目录 | 196 | | `$PATH` | 用分号分隔的目录列表,shell会到这些目录中查找命令 | 197 | | `$PWD` | 当前工作目录 | 198 | | `$RANDOM` | 0到32767之间的整数 | 199 | | `$UID` | 数值类型,当前用户的用户ID | 200 | | `$PS1` | 主要系统输入提示符 | 201 | | `$PS2` | 次要系统输入提示符 | 202 | 203 | [这里](http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html#sect_03_02_04)有一张更全面的Bash环境变量列表。 204 | 205 | ## 位置参数 206 | 207 | **位置参数** 是在调用一个函数并传给它参数时创建的变量。下表列出了在函数中,位置参数变量和一些其它的特殊变量以及它们的意义。 208 | 209 | | Parameter | Description | 210 | | :------------- | :---------------------------------------------------------- | 211 | | `$0` | 脚本名称 | 212 | | `$1 … $9` | 第1个到第9个参数列表 | 213 | | `${10} … ${N}` | 第10个到N个参数列表 | 214 | | `$*` or `$@` | 除了`$0`外的所有位置参数 | 215 | | `$#` | 不包括`$0`在内的位置参数的个数 | 216 | | `$FUNCNAME` | 函数名称(仅在函数内部有值) | 217 | 218 | 在下面的例子中,位置参数为:`$0='./script.sh'`,`$1='foo'`,`$2='bar'`: 219 | 220 | ./script.sh foo bar 221 | 222 | 变量可以有 _默认_ 值。我们可以用如下语法来指定默认值: 223 | 224 | ```bash 225 | # 如果变量为空,赋给他们默认值 226 | : ${VAR:='default'} 227 | : ${$1:='first'} 228 | # 或者 229 | FOO=${FOO:-'default'} 230 | ``` 231 | 232 | # Shell扩展 233 | 234 | _扩展_ 发生在一行命令被分成一个个的 _记号(tokens)_ 之后。换言之,扩展是一种执行数学运算的机制,还可以用来保存命令的执行结果,等等。 235 | 236 | 感兴趣的话可以阅读[关于shell扩展的更多细节](https://www.gnu.org/software/bash/manual/bash.html#Shell-Expansions)。 237 | 238 | ## 大括号扩展 239 | 240 | 大括号扩展让生成任意的字符串成为可能。它跟 _文件名扩展_ 很类似,举个例子: 241 | 242 | ```bash 243 | echo beg{i,a,u}n # begin began begun 244 | ``` 245 | 246 | 大括号扩展还可以用来创建一个可被循环迭代的区间。 247 | 248 | ```bash 249 | echo {0..5} # 0 1 2 3 4 5 250 | echo {00..8..2} # 00 02 04 06 08 251 | ``` 252 | 253 | ## 命令置换 254 | 255 | 命令置换允许我们对一个命令求值,并将其值置换到另一个命令或者变量赋值表达式中。当一个命令被``` `` ```或`$()`包围时,命令置换将会执行。举个例子: 256 | 257 | ```bash 258 | now=`date +%T` 259 | # or 260 | now=$(date +%T) 261 | 262 | echo $now # 19:08:26 263 | ``` 264 | 265 | ## 算数扩展 266 | 267 | 在bash中,执行算数运算是非常方便的。算数表达式必须包在`$(( ))`中。算数扩展的格式为: 268 | 269 | ```bash 270 | result=$(( ((10 + 5*3) - 7) / 2 )) 271 | echo $result # 9 272 | ``` 273 | 274 | 在算数表达式中,使用变量无需带上`$`前缀: 275 | 276 | ```bash 277 | x=4 278 | y=7 279 | echo $(( x + y )) # 11 280 | echo $(( ++x + y++ )) # 12 281 | echo $(( x + y )) # 13 282 | ``` 283 | 284 | ## 单引号和双引号 285 | 286 | 单引号和双引号之间有很重要的区别。在双引号中,变量引用或者命令置换是会被展开的。在单引号中是不会的。举个例子: 287 | 288 | ```bash 289 | echo "Your home: $HOME" # Your home: /Users/ 290 | echo 'Your home: $HOME' # Your home: $HOME 291 | ``` 292 | 293 | 当局部变量和环境变量包含空格时,它们在引号中的扩展要格外注意。随便举个例子,假如我们用`echo`来输出用户的输入: 294 | 295 | ```bash 296 | INPUT="A string with strange whitespace." 297 | echo $INPUT # A string with strange whitespace. 298 | echo "$INPUT" # A string with strange whitespace. 299 | ``` 300 | 301 | 调用第一个`echo`时给了它5个单独的参数 —— $INPUT被分成了单独的词,`echo`在每个词之间打印了一个空格。第二种情况,调用`echo`时只给了它一个参数(整个$INPUT的值,包括其中的空格)。 302 | 303 | 来看一个更严肃的例子: 304 | 305 | ```bash 306 | FILE="Favorite Things.txt" 307 | cat $FILE # 尝试输出两个文件: `Favorite` 和 `Things.txt` 308 | cat "$FILE" # 输出一个文件: `Favorite Things.txt` 309 | ``` 310 | 311 | 尽管这个问题可以通过把FILE重命名成`Favorite-Things.txt`来解决,但是,假如这个值来自某个环境变量,来自一个位置参数,或者来自其它命令(`find`, `cat`, 等等)呢。因此,如果输入 *可能* 包含空格,务必要用引号把表达式包起来。 312 | 313 | # 数组 314 | 315 | 跟其它程序设计语言一样,bash中的数组变量给了你引用多个值的能力。在bash中,数组下标也是从0开始,也就是说,第一个元素的下标是0。 316 | 317 | 跟数组打交道时,要注意一个特殊的环境变量`IFS`。**IFS**,全称 **Input Field Separator**,保存了数组中元素的分隔符。它的默认值是一个空格`IFS=' '`。 318 | 319 | ## 数组声明 320 | 321 | 在bash中,可以通过简单地给数组变量的某个下标赋值来创建一个数组: 322 | 323 | ```bash 324 | fruits[0]=Apple 325 | fruits[1]=Pear 326 | fruits[2]=Plum 327 | ``` 328 | 329 | 数组变量也可以通过复合赋值的方式来创建,比如: 330 | 331 | ```bash 332 | fruits=(Apple Pear Plum) 333 | ``` 334 | 335 | ## 数组扩展 336 | 337 | 单个数组元素的扩展跟普通变量的扩展类似: 338 | 339 | ```bash 340 | echo ${fruits[1]} # Pear 341 | ``` 342 | 343 | 整个数组可以通过把数字下标换成`*`或`@`来扩展: 344 | 345 | ```bash 346 | echo ${fruits[*]} # Apple Pear Plum 347 | echo ${fruits[@]} # Apple Pear Plum 348 | ``` 349 | 350 | 上面两行有很重要(也很微妙)的区别,假设某数组元素中包含空格: 351 | 352 | ```bash 353 | fruits[0]=Apple 354 | fruits[1]="Desert fig" 355 | fruits[2]=Plum 356 | ``` 357 | 358 | 为了将数组中每个元素单独一行输出,我们用内建的`printf`命令: 359 | 360 | ```bash 361 | printf "+ %s\n" ${fruits[*]} 362 | # + Apple 363 | # + Desert 364 | # + fig 365 | # + Plum 366 | ``` 367 | 368 | 为什么`Desert`和`fig`各占了一行?尝试用引号包起来: 369 | 370 | ```bash 371 | printf "+ %s\n" "${fruits[*]}" 372 | # + Apple Desert fig Plum 373 | ``` 374 | 375 | 现在所有的元素都跑去了一行 —— 这不是我们想要的!为了解决这个痛点,`${fruits[@]}`闪亮登场: 376 | 377 | ```bash 378 | printf "+ %s\n" "${fruits[@]}" 379 | # + Apple 380 | # + Desert fig 381 | # + Plum 382 | ``` 383 | 384 | 在引号内,`${fruits[@]}`将数组中的每个元素扩展为一个单独的参数;数组元素中的空格得以保留。 385 | 386 | ## 数组切片 387 | 388 | 除此之外,可以通过 _切片_ 运算符来取出数组中的某一片元素: 389 | 390 | ```bash 391 | echo ${fruits[@]:0:2} # Apple Desert fig 392 | ``` 393 | 394 | 在上面的例子中,`${fruits[@]}`扩展为整个数组,`:0:2`取出了数组中从0开始,长度为2的元素。 395 | 396 | ## 向数组中添加元素 397 | 398 | 向数组中添加元素也非常简单。复合赋值在这里显得格外有用。我们可以这样做: 399 | 400 | ```bash 401 | fruits=(Orange "${fruits[@]}" Banana Cherry) 402 | echo ${fruits[@]} # Orange Apple Desert fig Plum Banana Cherry 403 | ``` 404 | 405 | 上面的例子中,`${fruits[@]}`扩展为整个数组,并被置换到复合赋值语句中,接着,对数组`fruits`的赋值覆盖了它原来的值。 406 | 407 | ## 从数组中删除元素 408 | 409 | 用`unset`命令来从数组中删除一个元素: 410 | 411 | ```bash 412 | unset fruits[0] 413 | echo ${fruits[@]} # Apple Desert fig Plum Banana Cherry 414 | ``` 415 | 416 | # 流,管道以及序列 417 | 418 | Bash有很强大的工具来处理程序之间的协同工作。使用流,我们能将一个程序的输出发送到另一个程序或文件,因此,我们能方便地记录日志或做一些其它我们想做的事。 419 | 420 | 管道给了我们创建传送带的机会,控制程序的执行成为可能。 421 | 422 | 学习如何使用这些强大的、高级的工具是非常非常重要的。 423 | 424 | ## 流 425 | 426 | Bash接收输入,并以字符序列或 **字符流** 的形式产生输出。这些流能被重定向到文件或另一个流中。 427 | 428 | 有三个文件描述符: 429 | 430 | | 代码 | 描述符 | 描述 | 431 | | :--: | :--------: | :------------------- | 432 | | `0` | `stdin` | 标准输入 | 433 | | `1` | `stdout` | 标准输出 | 434 | | `2` | `stderr` | 标准错误输出 | 435 | 436 | 重定向让我们可以控制一个命令的输入来自哪里,输出结果到什么地方。这些运算符在控制流的重定向时会被用到: 437 | 438 | | Operator | Description | 439 | | :------: | :------------------------------------------- | 440 | | `>` | 重定向输出 | 441 | | `&>` | 重定向输出和错误输出 | 442 | | `&>>` | 以附加的形式重定向输出和错误输出 | 443 | | `<` | 重定向输入 | 444 | | `<<` | [Here文档](http://tldp.org/LDP/abs/html/here-docs.html) 语法 | 445 | | `<<<` | [Here字符串](http://www.tldp.org/LDP/abs/html/x17837.html) | 446 | 447 | 以下是一些使用重定向的例子: 448 | 449 | ```bash 450 | # ls的结果将会被写到list.txt中 451 | ls -l > list.txt 452 | 453 | # 将输出附加到list.txt中 454 | ls -a >> list.txt 455 | 456 | # 所有的错误信息会被写到errors.txt中 457 | grep da * 2> errors.txt 458 | 459 | # 从errors.txt中读取输入 460 | less < errors.txt 461 | ``` 462 | 463 | ## 管道 464 | 465 | 我们不仅能将流重定向到文件中,还能重定向到其它程序中。**管道** 允许我们把一个程序的输出当做另一个程序的输入。 466 | 467 | 在下面的例子中,`command1`把它的输出发送给了`command2`,然后输出被传递到`command3`: 468 | 469 | command1 | command2 | command3 470 | 471 | 这样的结构被称作 **管道**。 472 | 473 | 在实际操作中,这可以用来在多个程序间依次处理数据。在下面的例子中,`ls -l`的输出被发送给了`grep`,来打印出扩展名是`.md`的文件,它的输出最终发送给了`less`: 474 | 475 | ls -l | grep .md$ | less 476 | 477 | 管道的返回值通常是管道中最后一个命令的返回值。shell会等到管道中所有的命令都结束后,才会返回一个值。如果你想让管道中任意一个命令失败后,管道就宣告失败,那么需要用下面的命令设置pipefail选项: 478 | 479 | set -o pipefail 480 | 481 | ## 命令序列 482 | 483 | 命令序列是由`;`,`&`,`&&`或者`||`运算符分隔的一个或多个管道序列。 484 | 485 | 如果一个命令以`&`结尾,shell将会在一个子shell中异步执行这个命令。换句话说,这个命令将会在后台执行。 486 | 487 | 以`;`分隔的命令将会依次执行:一个接着一个。shell会等待直到每个命令执行完。 488 | 489 | ```bash 490 | # command2 会在 command1 之后执行 491 | command1 ; command2 492 | 493 | # 等同于这种写法 494 | command1 495 | command2 496 | ``` 497 | 498 | 以`&&`和`||`分隔的命令分别叫做 _与_ 和 _或_ 序列。 499 | 500 | _与序列_ 看起来是这样的: 501 | 502 | ```bash 503 | # 当且仅当command1执行成功(返回0值)时,command2才会执行 504 | command1 && command2 505 | ``` 506 | 507 | _或序列_ 是下面这种形式: 508 | 509 | ```bash 510 | # 当且仅当command1执行失败(返回错误码)时,command2才会执行 511 | command1 || command2 512 | ``` 513 | 514 | _与_ 或 _或_ 序列的返回值是序列中最后一个执行的命令的返回值。 515 | 516 | # 条件语句 517 | 518 | 跟其它程序设计语言一样,Bash中的条件语句让我们可以决定一个操作是否被执行。结果取决于一个包在`[[ ]]`里的表达式。 519 | 520 | 条件表达式可以包含`&&`和`||`运算符,分别对应 _与_ 和 _或_ 。除此之外还有很多有用的[表达式](#基元和组合表达式)。 521 | 522 | 共有两个不同的条件表达式:`if`和`case`。 523 | 524 | ## 基元和组合表达式 525 | 526 | 由`[[ ]]`(`sh`中是`[ ]`)包起来的表达式被称作 **检测命令** 或 **基元**。这些表达式帮助我们检测一个条件的结果。在下面的表里,为了兼容`sh`,我们用的是`[ ]`。这里可以找到有关[bash中单双中括号区别](http://serverfault.com/a/52050)的答案。 527 | 528 | **跟文件系统相关:** 529 | 530 | | 基元 | 含义 | 531 | | :-----------: | :----------------------------------------------------------- | 532 | | `[ -e FILE ]` | 如果`FILE`存在 (**e**xists),为真 | 533 | | `[ -f FILE ]` | 如果`FILE`存在且为一个普通文件(**f**ile),为真 | 534 | | `[ -d FILE ]` | 如果`FILE`存在且为一个目录(**d**irectory),为真 | 535 | | `[ -s FILE ]` | 如果`FILE`存在且非空(**s**ize 大于0),为真 | 536 | | `[ -r FILE ]` | 如果`FILE`存在且有读权限(**r**eadable),为真 | 537 | | `[ -w FILE ]` | 如果`FILE`存在且有写权限(**w**ritable),为真 | 538 | | `[ -x FILE ]` | 如果`FILE`存在且有可执行权限(e**x**ecutable),为真 | 539 | | `[ -L FILE ]` | 如果`FILE`存在且为一个符号链接(**l**ink),为真 | 540 | | `[ FILE1 -nt FILE2 ]` | `FILE1`比`FILE2`新(**n**ewer **t**han) | 541 | | `[ FILE1 -ot FILE2 ]` | `FILE1`比`FILE2`旧(**o**lder **t**han) | 542 | 543 | **跟字符串相关:** 544 | 545 | | 基元 | 含义 | 546 | | :------------: | :---------------------------------------------------------- | 547 | | `[ -z STR ]` | `STR`为空(长度为0,**z**ero) | 548 | | `[ -n STR ]` | `STR`非空(长度非0,**n**on-zero) | 549 | | `[ STR1 == STR2 ]` | `STR1`和`STR2`相等 | 550 | | `[ STR1 != STR2 ]` | `STR1`和`STR2`不等 | 551 | 552 | **算数二元运算符:** 553 | 554 | | 基元 | 含义 | 555 | | :-----------------: | :----------------------------------------------------- | 556 | | `[ ARG1 -eq ARG2 ]` | `ARG1`和`ARG2`相等(**eq**ual) | 557 | | `[ ARG1 -ne ARG2 ]` | `ARG1`和`ARG2`不等(**n**ot **e**qual) | 558 | | `[ ARG1 -lt ARG2 ]` | `ARG1`小于`ARG2`(**l**ess **t**han) | 559 | | `[ ARG1 -le ARG2 ]` | `ARG1`小于等于`ARG2`(**l**ess than or **e**qual) | 560 | | `[ ARG1 -gt ARG2 ]` | `ARG1`大于`ARG2`(**g**reater **t**han) | 561 | | `[ ARG1 -ge ARG2 ]` | `ARG1`大于等于`ARG2`(**g**reater than or **e**qual) | 562 | 563 | 条件语句可以跟 **组合表达式** 配合使用: 564 | 565 | | Operation | Effect | 566 | | :------------: | :---------------------------------------------------------- | 567 | | `[ ! EXPR ]` | 如果`EXPR`为假,为真 | 568 | | `[ (EXPR) ]` | 返回`EXPR`的值 | 569 | | `[ EXPR1 -a EXPR2 ]` | 逻辑 _与_, 如果`EXPR1`和(**a**nd)`EXPR2`都为真,为真 | 570 | | `[ EXPR1 -o EXPR2 ]` | 逻辑 _或_, 如果`EXPR1`或(**o**r)`EXPR2`为真,为真 | 571 | 572 | 当然,还有很多有用的基元,在[Bash的man页面](http://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html)能很容易找到它们。 573 | 574 | ## 使用`if` 575 | 576 | `if`在使用上跟其它语言相同。如果中括号里的表达式为真,那么`then`和`fi`之间的代码会被执行。`fi`标志着条件代码块的结束。 577 | 578 | ```bash 579 | # 写成一行 580 | if [[ 1 -eq 1 ]]; then echo "true"; fi 581 | 582 | # 写成多行 583 | if [[ 1 -eq 1 ]]; then 584 | echo "true" 585 | fi 586 | ``` 587 | 588 | 同样,我们可以使用`if..else`语句,例如: 589 | 590 | ```bash 591 | # 写成一行 592 | if [[ 2 -ne 1 ]]; then echo "true"; else echo "false"; fi 593 | 594 | # 写成多行 595 | if [[ 2 -ne 1 ]]; then 596 | echo "true" 597 | else 598 | echo "false" 599 | fi 600 | ``` 601 | 602 | 有些时候,`if..else`不能满足我们的要求。别忘了`if..elif..else`,使用起来也很方便。 603 | 604 | 看下面的例子: 605 | 606 | ```bash 607 | if [[ `uname` == "Adam" ]]; then 608 | echo "Do not eat an apple!" 609 | elif [[ `uname` == "Eva" ]]; then 610 | echo "Do not take an apple!" 611 | else 612 | echo "Apples are delicious!" 613 | fi 614 | ``` 615 | 616 | ## 使用`case` 617 | 618 | 如果你需要面对很多情况,分别要采取不同的措施,那么使用`case`会比嵌套的`if`更有用。使用`case`来解决复杂的条件判断,看起来像下面这样: 619 | 620 | ```bash 621 | case "$extension" in 622 | "jpg"|"jpeg") 623 | echo "It's image with jpeg extension." 624 | ;; 625 | "png") 626 | echo "It's image with png extension." 627 | ;; 628 | "gif") 629 | echo "Oh, it's a giphy!" 630 | ;; 631 | *) 632 | echo "Woops! It's not image!" 633 | ;; 634 | esac 635 | ``` 636 | 637 | 每种情况都是匹配了某个模式的表达式。`|`用来分割多个模式,`)`用来结束一个模式序列。第一个匹配上的模式对应的命令将会被执行。`*`代表任何不匹配以上给定模式的模式。命令块儿之间要用`;;`分隔。 638 | 639 | # 循环 640 | 641 | 循环其实不足为奇。跟其它程序设计语言一样,bash中的循环也是只要控制条件为真就一直迭代执行的代码块。 642 | 643 | Bash中有四种循环:`for`,`while`,`until`和`select`。 644 | 645 | ## `for`循环 646 | 647 | `for`与它在C语言中的姊妹非常像。看起来是这样: 648 | 649 | ```bash 650 | for arg in elem1 elem2 ... elemN 651 | do 652 | # 语句 653 | done 654 | ``` 655 | 656 | 在每次循环的过程中,`arg`依次被赋值为从`elem1`到`elemN`。这些值还可以是通配符或者[大括号扩展](#大括号扩展)。 657 | 658 | 当然,我们还可以把`for`循环写在一行,但这要求`do`之前要有一个分号,就像下面这样: 659 | 660 | ```bash 661 | for i in {1..5}; do echo $i; done 662 | ``` 663 | 664 | 还有,如果你觉得`for..in..do`对你来说有点奇怪,那么你也可以像C语言那样使用`for`,比如: 665 | 666 | ```bash 667 | for (( i = 0; i < 10; i++ )); do 668 | echo $i 669 | done 670 | ``` 671 | 672 | 当我们想对一个目录下的所有文件做同样的操作时,`for`就很方便了。举个例子,如果我们想把所有的`.bash`文件移动到`script`文件夹中,并给它们可执行权限,我们的脚本可以这样写: 673 | 674 | ```bash 675 | #!/bin/bash 676 | 677 | for FILE in $HOME/*.bash; do 678 | mv "$FILE" "${HOME}/scripts" 679 | chmod +x "${HOME}/scripts/${FILE}" 680 | done 681 | ``` 682 | 683 | ## `while`循环 684 | 685 | `while`循环检测一个条件,只要这个条件为 _真_,就执行一段命令。被检测的条件跟`if..then`中使用的[基元](#基元和组合表达式)并无二异。因此一个`while`循环看起来会是这样: 686 | 687 | ```bash 688 | while [[ condition ]] 689 | do 690 | # 语句 691 | done 692 | ``` 693 | 694 | 跟`for`循环一样,如果我们把`do`和被检测的条件写到一行,那么必须要在`do`之前加一个分号。 695 | 696 | 比如下面这个例子: 697 | 698 | ```bash 699 | #!/bin/bash 700 | 701 | # 0到9之间每个数的平方 702 | x=0 703 | while [[ $x -lt 10 ]]; do # x小于10 704 | echo $(( x * x )) 705 | x=$(( x + 1 )) # x加1 706 | done 707 | ``` 708 | 709 | ## `until`循环 710 | 711 | `until`循环跟`while`循环正好相反。它跟`while`一样也需要检测一个测试条件,但不同的是,只要该条件为 _假_ 就一直执行循环: 712 | 713 | ```bash 714 | until [[ condition ]]; do 715 | # 语句 716 | done 717 | ``` 718 | 719 | ## `select`循环 720 | 721 | `select`循环帮助我们组织一个用户菜单。它的语法几乎跟`for`循环一致: 722 | 723 | ```bash 724 | select answer in elem1 elem2 ... elemN 725 | do 726 | # 语句 727 | done 728 | ``` 729 | 730 | `select`会打印`elem1..elemN`以及它们的序列号到屏幕上,之后会提示用户输入。通常看到的是`$?`(`PS3`变量)。用户的选择结果会被保存到`answer`中。如果`answer`是一个在`1..N`之间的数字,那么`语句`会被执行,紧接着会进行下一次迭代 —— 如果不想这样的话我们可以使用`break`语句。 731 | 732 | 一个可能的实例可能会是这样: 733 | 734 | ```bash 735 | #!/bin/bash 736 | 737 | PS3="Choose the package manager: " 738 | select ITEM in bower npm gem pip 739 | do 740 | echo -n "Enter the package name: " && read PACKAGE 741 | case $ITEM in 742 | bower) bower install $PACKAGE ;; 743 | npm) npm install $PACKAGE ;; 744 | gem) gem install $PACKAGE ;; 745 | pip) pip install $PACKAGE ;; 746 | esac 747 | break # 避免无限循环 748 | done 749 | ``` 750 | 751 | 这个例子,先询问用户他想使用什么包管理器。接着,又询问了想安装什么包,最后执行安装操作。 752 | 753 | 运行这个脚本,会得到如下输出: 754 | 755 | ``` 756 | $ ./my_script 757 | 1) bower 758 | 2) npm 759 | 3) gem 760 | 4) pip 761 | Choose the package manager: 2 762 | Enter the package name: bash-handbook 763 | 764 | ``` 765 | 766 | ## 循环控制 767 | 768 | 我们会遇到想提前结束一个循环或跳过某次循环执行的情况。这些可以使用shell内建的`break`和`continue`语句来实现。它们可以在任何循环中使用。 769 | 770 | `break`语句用来提前结束当前循环。我们之前已经见过它了。 771 | 772 | `continue`语句用来跳过某次迭代。我们可以这么来用它: 773 | 774 | ```bash 775 | for (( i = 0; i < 10; i++ )); do 776 | if [[ $(( i % 2 )) -eq 0 ]]; then continue; fi 777 | echo $i 778 | done 779 | ``` 780 | 781 | 运行上面的例子,会打印出所有0到9之间的奇数。 782 | 783 | # 函数 784 | 785 | 在脚本中,我们可以定义并调用函数。跟其它程序设计语言类似,函数是一个代码块,但有所不同。 786 | 787 | bash中,函数是一个命令序列,这个命令序列组织在某个名字下面,即 _函数名_ 。调用函数跟其它语言一样,写下函数名字,函数就会被 _调用_ 。 788 | 789 | 我们可以这样声明函数: 790 | 791 | ```bash 792 | my_func () { 793 | # 语句 794 | } 795 | 796 | my_func # 调用 my_func 797 | ``` 798 | 799 | 我们必须在调用前声明函数。 800 | 801 | 函数可以接收参数并返回结果 —— 返回值。参数,在函数内部,跟[非交互式](#非交互模式)下的脚本参数处理方式相同 —— 使用[位置参数](#位置参数)。返回值可以使用`return`命令 _返回_ 。 802 | 803 | 下面这个函数接收一个名字参数,返回`0`,表示成功执行。 804 | 805 | ```bash 806 | # 带参数的函数 807 | greeting () { 808 | if [[ -n $1 ]]; then 809 | echo "Hello, $1!" 810 | else 811 | echo "Hello, unknown!" 812 | fi 813 | return 0 814 | } 815 | 816 | greeting Denys # Hello, Denys! 817 | greeting # Hello, unknown! 818 | ``` 819 | 820 | 我们之前已经介绍过[返回值](#返回值)。不带任何参数的`return`会返回最后一个执行的命令的返回值。上面的例子,`return 0`会返回一个成功表示执行的值,`0`。 821 | 822 | ## Debugging 823 | 824 | shell提供了用于debugging脚本的工具。如果我们想以debug模式运行某脚本,可以在其shebang中使用一个特殊的选项: 825 | 826 | ```bash 827 | #!/bin/bash options 828 | ``` 829 | 830 | options是一些可以改变shell行为的选项。下表是一些可能对你有用的选项: 831 | 832 | | Short | Name | Description | 833 | | :---: | :---------- | :----------------------------------------------------- | 834 | | `-f` | noglob | 禁止文件名展开(globbing) | 835 | | `-i` | interactive | 让脚本以 _交互_ 模式运行 | 836 | | `-n` | noexec | 读取命令,但不执行(语法检查) | 837 | | `-t` | — | 执行完第一条命令后退出 | 838 | | `-v` | verbose | 在执行每条命令前,向`stderr`输出该命令 | 839 | | `-x` | xtrace | 在执行每条命令前,向`stderr`输出该命令以及该命令的扩展参数 | 840 | 841 | 举个例子,如果我们在脚本中指定了`-x`例如: 842 | 843 | ```bash 844 | #!/bin/bash -x 845 | 846 | for (( i = 0; i < 3; i++ )); do 847 | echo $i 848 | done 849 | ``` 850 | 851 | 这会向`stdout`打印出变量的值和一些其它有用的信息: 852 | 853 | ``` 854 | $ ./my_script 855 | + (( i = 0 )) 856 | + (( i < 3 )) 857 | + echo 0 858 | 0 859 | + (( i++ )) 860 | + (( i < 3 )) 861 | + echo 1 862 | 1 863 | + (( i++ )) 864 | + (( i < 3 )) 865 | + echo 2 866 | 2 867 | + (( i++ )) 868 | + (( i < 3 )) 869 | ``` 870 | 871 | 有时我们需要debug脚本的一部分。这种情况下,使用`set`命令会很方便。这个命令可以启用或禁用选项。使用`-`启用选项,`+`禁用选项: 872 | 873 | ```bash 874 | #!/bin/bash 875 | 876 | echo "xtrace is turned off" 877 | set -x 878 | echo "xtrace is enabled" 879 | set +x 880 | echo "xtrace is turned off again" 881 | ``` 882 | 883 | # 后记 884 | 885 | 我希望这本小小的册子能很有趣且很有帮助。老实说,我写这本小册子是为了自己不会忘了bash的基础知识。我尽量让文字简明达意,希望你们会喜欢。 886 | 887 | 这本小册子讲述了我自己的Bash经验。它并非全面综合,因此如果你想了解更多,请运行`man bash`,从那里开始。 888 | 889 | 非常欢迎您的贡献,任何指正和问题我都非常感激。这些都可以通过创建一个[issue](#https://github.com/liushuaikobe/bash-handbook-zh-CN/issues)来进行。 890 | 891 | 感谢您的阅读! 892 | 893 | # 想了解更多? 894 | 895 | 下面是一些其它有关Bash的资料: 896 | 897 | * Bash的man页面。在Bash可以运行的众多环境中,通过运行`man bash`可以借助帮助系统`man`来显示Bash的帮助信息。有关`man`命令的更多信息,请看托管在[The Linux Information Project](http://www.linfo.org/)上的网页["The man Command"](http://www.linfo.org/man.html)。 898 | * ["Bourne-Again SHell manual"](https://www.gnu.org/software/bash/manual/),有很多可选的格式,包括HTML,Info,Tex,PDF,以及Textinfo。托管在上。截止到2016/01,它基于的是Bash的4.3版本,最后更新日期是2015/02/02。 899 | 900 | # 其它资源 901 | 902 | * [awesome-bash](https://github.com/awesome-lists/awesome-bash),是一个组织有序的有关Bash脚本以及相关资源的列表 903 | * [awesome-shell](https://github.com/alebcay/awesome-shell),另一个组织有序的shell资源列表 904 | * [bash-it](https://github.com/Bash-it/bash-it),为你日常使用,开发以及维护shell脚本和自定义命令提供了一个可靠的框架 905 | * [dotfiles.github.io](http://dotfiles.github.io/),上面有bash和其它shell的各种dotfiles集合以及shell框架的链接 906 | * [learnyoubash](https://github.com/denysdovhan/learnyoubash),帮助你编写你的第一个bash脚本 907 | * [shellcheck](https://github.com/koalaman/shellcheck),一个shell脚本的静态分析工具,既可以在网页[www.shellcheck.net](http://www.shellcheck.net/)上使用它,又可以在命令行中使用,安装教程在[koalaman/shellcheck](https://github.com/koalaman/shellcheck)的github仓库页面上 908 | 909 | 最后,Stack Overflow上[bash标签下](https://stackoverflow.com/questions/tagged/bash)有很多你可以学习的问题,当你遇到问题时,也是一个提问的好地方。 910 | 911 | # License 912 | 913 | [![CC 4.0][cc-image]][cc-url] © [Denys Dovhan](http://denysdovhan.com) 914 | 915 | [cc-url]: http://creativecommons.org/licenses/by/4.0/ 916 | [cc-image]: https://i.creativecommons.org/l/by/4.0/80x15.png 917 | 918 | [![CC 4.0][cc-image]][cc-url] © [Shuai Liu](http://blog.vars.me) For Chinese translation 919 | 920 | [cc-url]: http://creativecommons.org/licenses/by/4.0/ 921 | [cc-image]: https://i.creativecommons.org/l/by/4.0/80x15.png 922 | 923 | # [译者手记](./translator-notes.md) 924 | -------------------------------------------------------------------------------- /translations/zh-TW/README.md: -------------------------------------------------------------------------------- 1 | # bash-handbook [![CC 4.0][cc-image]][cc-url] 2 | 3 | 這份文件是為想要學習 Bash 但是又不想要專研太深的人。 4 | 5 | > **Tip**:不妨嘗試 [**learnyoubash**](https://git.io/learnyoubash) — 一個基於本文件的互動教學平台! 6 | 7 | # Node 套件手稿 8 | 9 | 你可以使用 `npm` 來安裝這份手冊。只要執行: 10 | 11 | ``` 12 | $ npm install -g bash-handbook 13 | ``` 14 | 15 | 你現在可以在命令執行 `bash-handbook`。它會開啟你所選的 `$PAGER`。反之,你也可以繼續在這裡閱讀。 16 | 17 | 來源是這裡: 18 | 19 | # 翻譯 20 | 21 | 目前翻譯這個 **bash-handbook** 的版本有: 22 | 23 | - [Português (Brasil)](/translations/pt-BR/README.md) 24 | - [简体中文 (中国)](/translations/zh-CN/README.md) 25 | - [繁體中文(台灣)](/translations/zh-TW/README.md) 26 | 27 | [**徵求其他語言版本翻譯**][tr-request] 28 | 29 | [tr-request]: https://github.com/denysdovhan/bash-handbook/issues/new?title=Translation%20Request:%20%5BPlease%20enter%20language%20here%5D&body=I%20am%20able%20to%20translate%20this%20language%20%5Byes/no%5D 30 | 31 | # 目錄 32 | 33 | - [簡介](#簡介) 34 | - [關於 Shell 以及 Shell 的模式](#關於-shell-以及-shell-的模式) 35 | - [互動模式](#互動模式) 36 | - [非互動模式](#非互動模式) 37 | - [Exit codes](#exit-codes) 38 | - [註解](#註解) 39 | - [變數](#變數) 40 | - [區域變數](#區域變數) 41 | - [環境變數](#環境變數) 42 | - [命令列引數](#命令列引數) 43 | - [Shell expansion](#shell-expansion) 44 | - [括號展開](#括號展開) 45 | - [命令替代](#命令替代) 46 | - [算術代入展開](#算術代入展開) 47 | - [單引號和雙引號](#單引號和雙引號) 48 | - [陣列](#陣列) 49 | - [陣列宣告](#陣列宣告) 50 | - [陣列展開](#陣列展開) 51 | - [切割陣列](#切割陣列) 52 | - [把一個物件加入到陣列中](#把一個物件加入到陣列中) 53 | - [把一個物件從陣列中移除](#把一個物件從陣列中移除) 54 | - [Streams、pipes 和命令序列](#streamspipes-和-lists) 55 | - [Streams](#streams) 56 | - [Pipes](#pipes) 57 | - [命令序列](#命令序列) 58 | - [條件陳述式](#條件陳述式) 59 | - [Primary 和組合表達式](#primary-和組合表達式) 60 | - [使用 `if` 陳述](#使用-if-陳述) 61 | - [使用 `case` 陳述](#使用-case-陳述) 62 | - [迴圈](#迴圈) 63 | - [`for` 迴圈](#for-迴圈) 64 | - [`while` 迴圈](#while-迴圈) 65 | - [`until` 迴圈](#until-迴圈) 66 | - [`select` 迴圈](#select-迴圈) 67 | - [迴圈控制](#迴圈控制) 68 | - [函式](#函式) 69 | - [Debug](#debug) 70 | - [後記](#後記) 71 | - [其他資源](#其他資源) 72 | - [License](#license) 73 | 74 | # 簡介 75 | 76 | 如果你是一個開發者,你一定知道時間的寶貴,那麼使工作流程更快速簡單便是一件很重要的事情。 77 | 78 | 為了讓工作更有效率及更高的產能,常常會有些重複繁瑣的事情,像是: 79 | 80 | * 截圖並把圖片上傳到伺服器。 81 | * 需要處理各式各樣有著不同格式的文件。 82 | * 在不同檔案格式間進行轉換。 83 | * 解析程式的輸出。 84 | 85 | 進入 **Bash** 的世界,coder 們的救星! 86 | 87 | Bash 是一個由 [Brain Fox](https://en.wikipedia.org/wiki/Brian_Fox_(computer_programmer)) 為 GNU 專案開發的 Unix Shell,這是一個為取代 [Bourne shell](https://en.wikipedia.org/wiki/Bourne_shell) 而開發的自由軟體,最初在 1989 年被發布,並且 Bash 也作為 Linux 和 OSX 的 default shell 很長一段時間了。 88 | 89 | 那麼我們為什麼需要學習有著 30 年歷史的東西呢?答案很簡單:這個東西是當今最強大的且易於攜帶的工具,在所有 Unix-based 的作業系統撰寫有效率的的腳本程式。而這就是你需要學習 bash 的原因。 90 | 91 | 在這個手冊中,我會透過範例去說明 bash 最重要的概念。而我也希望這篇概略性的文章對你會有幫助。 92 | 93 | # 關於 Shell 以及 Shell 的模式 94 | 95 | 使用者 bash shell 可以工作在兩種模式 - 互動模式及非互動模式。 96 | 97 | ## 互動模式 98 | 99 | 如果你是在 Ubuntu 上工作,你有七個虛擬終端機可以讓你使用。 100 | 桌面環境是被放在第七個終端機中, 101 | 所以你可以使用 `Ctrl-Alt-F7` 快捷鍵來回到友善的 GUI 介面。 102 | 103 | 你可以使用 `Ctrl-Alt-F1` 來開始使用 shell。在那之後,熟悉的 GUI 介面將會消失,取而代之的則是虛擬終端機。 104 | 105 | 如果你看到以下的東西,代表你處在互動模式: 106 | 107 | user@host:~$ 108 | 109 | 你可以輸入各種 Unix 的命令,像是 `ls`、`grep`、`cd`、`mkdir`、`rm`,然後看看他們執行後的結果。 110 | 111 | 我們稱這個為互動模式,因為 shell 是直接與使用者作互動。 112 | 113 | 但使用虛擬終端機實在是不怎麼方便。例如,假設你想要同時編輯文件和執行其他命令,在這樣的情況下,使用虛擬終端機模擬器(virtual terminal emulators)會比較好,像是: 114 | 115 | - [GNOME Terminal](https://en.wikipedia.org/wiki/GNOME_Terminal) 116 | - [Terminator](https://en.wikipedia.org/wiki/Terminator_(terminal_emulator)) 117 | - [iTerm2](https://en.wikipedia.org/wiki/ITerm2) 118 | - [ConEmu](https://en.wikipedia.org/wiki/ConEmu) 119 | 120 | ## 非互動模式 121 | 122 | 在非互動模式中,shell 從檔案或是 pipe 讀取到命令然後執行他們。當直譯器到達檔案的末端(EOF, End Of Line)時,shell 程式終止 session 並返回到父程式。 123 | 124 | 使用以下的命令來執行,使 shell 在非互動模式中執行: 125 | 126 | sh /path/to/script.sh 127 | bash /path/to/script.sh 128 | 129 | 在上面的例子中,`script.sh` 其實只是一個普通的檔案,而 shell 直譯器可以解析檔案中的命令;`sh` 和 `bash` 是 shell 的直譯程式。你可以使用你喜歡的編輯器來建立 `script.sh`(例如:vim、nano、Sublime Text、Atom 等等)。 130 | 131 | 除此之外,你還可以透過 `chmod` 命令給予檔案執行權限,使檔案可以執行: 132 | 133 | 134 | chmod +x /path/to/script.sh 135 | 136 | 另外,在這個 script 第一行必須指定應該使用哪個程式來執行這個檔案,像是: 137 | 138 | ```bash 139 | #!/bin/bash 140 | echo "Hello, world!" 141 | ``` 142 | 143 | 或者,如果你偏好使用 `sh` 而不是 `bash`,將 `#!/bin/bash` 改成 `#!/bin/sh`。這個 `#!` 字元序列被稱為 [shebang](http://en.wikipedia.org/wiki/Shebang_%28Unix%29)。現在你可以這樣執行這個 script: 144 | 145 | /path/to/script.sh 146 | 147 | 我們使用上面簡單方便的技巧使用 `echo` 將文字印到螢幕終端。 148 | 149 | 另一個使用 sheban 的方式: 150 | 151 | ```bash 152 | #!/usr/bin/env bash 153 | echo "Hello, world!" 154 | ``` 155 | 156 | 使用 shebang 的好處是可以基於環境變數 `PATH` 來尋找程式(這個例子是 `bash`)。相較於上面的第一種方法,應該使用這個方法比較好,因為我們不知道程式被放在系統的什麼地方。這樣好處是,如果 `PATH` 變數在系統上可能被設定到其他不同版本的程式上。例如,安裝一個新版本的 `bash` 同時保留原來版本的 `bash`,然後把新版本的路徑加入環境變數 `PATH` 中。使用 `#!/bin/bash` 會導致使用到原來的 `bash`,而使用 `#!/usr/bin/env bash` 會使用到新的版本。 157 | 158 | 159 | ## Exit codes 160 | 161 | 每個命令都會回傳一個 **exit code**(**回傳狀態**或**結束狀態**)。一個成功的命令始終回傳 `0`(零代碼),而失敗總是回傳非零值(錯誤代碼)。錯誤代碼必須介於 1 到 255 的正整數。 162 | 163 | 撰寫 script 時,我們可以使用另一個方便的 `exit` 命令。使用這個命令來終止目前的執行,並向 shell 提供一個 exit code。執行一個不帶有任何參數的 `exit` code,會終止執行目前的 script 並回傳在 `exit` 之前的最後一個命令的 exit code。 164 | 165 | 當程式終止時,shell 會把 **exit code** 指派給環境變數 `$?`。`$?` 變數是我們測試一個 script 是否成功執行。 166 | 167 | 用同樣的方式,我們可以使用 `exit` 來停止一個 script,我們能在函式中使用 `return` 來退出函式並回傳 **exit code** 給呼叫者,又或者你可以在函式中使用 `exit`,這會退出函式 _並_結束程式。 168 | 169 | # 註解 170 | 171 | Script 中可以包含註解。註解是一個特殊的語句,會被 `shell` 直譯器忽略。它們以一個 `#` 符號為開頭。 172 | 173 | 例如: 174 | 175 | ```bash 176 | #!/bin/bash 177 | # 這個 script 將會列印你的使用者名稱。 178 | whoami 179 | ``` 180 | 181 | > **Tip**: 使用註解可以解釋你的 script 做了什麼,以及_為什麼_這麼做。 182 | 183 | # 變數 184 | 185 | 186 | 與大部分的程式語言一樣,你可以在 bash 內建立變數。 187 | Bash 中沒有資料類型的概念。變數只能是純數字或是一或多個字元所組成的字串。你可以建立三種類型的變數:區域變數、環境變數、以及作為_命令列引數_的變數。 188 | 189 | ## 區域變數 190 | 191 | **區域變數**是只存在於單一 script 的變數,它們是無法讓其他程式或 script 存取的。 192 | 一個變數可以使用 `=` 來宣告(根據規則,在變數名稱、 `=` 、變數的值之間**不應有**空格的存在),你可以透過 `$` 來取得這個變數的值。例如: 193 | 194 | ```bash 195 | username="denysdovhan" # 宣告變數 196 | echo $username # 顯示變數 197 | unset username # 刪除變數 198 | ``` 199 | 200 | 我們也可以使用 `local` keyword 在一個 function 中宣告一個區域變數。當 function 結束時,這個區域變數就會消失。 201 | 202 | ```bash 203 | local local_var="I'm a local value" 204 | ``` 205 | 206 | ## 環境變數 207 | 208 | **環境變數**執行在目前 shell session 可以讓任何程式或 script 存取的變數。建立它們與區域變數類似,但使用 `export` 作為前綴 209 | 210 | ```bash 211 | export GLOBAL_VAR="I'm a global variable" 212 | ``` 213 | 214 | bash 中有_許多_全域變數。你會經常遇到這些全域變數,以下是最常使用到的變數查閱表︰ 215 | 216 | | 變數 | 描述 | 217 | | :----------- | :------------------------------------------------------------ | 218 | | `$HOME` | 目前使用者的家目錄 | 219 | | `$PATH` | shell 以這些冒號分隔的目錄清單尋找命令 | 220 | | `$PWD` | 目前的工作目錄 | 221 | | `$RANDOM` | 0 到 32767 之間的隨機整數 | 222 | | `$UID` | 數字類型,使用者的 ID | 223 | | `$PS1` | 主要的提示字串 | 224 | | `$PS2` | 次要的提示字串 | 225 | 226 | 根據這個[連結](http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html#sect_03_02_04),可以查看更多在 Bash 內的環境變數列表。 227 | 228 | ## 命令列引數 229 | 230 | **命令列引數**是當程式執行時,變數根據執行程式時,程式名稱後所跟隨的參數而得。下面表格列出了當你在程式執行時,命令列引數和其他特殊變數它們所代表的意義。 231 | 232 | 233 | | 參數 | 描述 | 234 | | :------------- | :------------------------------------------------------------ | 235 | | `$0` | Script 的名稱 | 236 | | `$1 … $9` | 從第 1 個到第 9 個參數 | 237 | | `${10} … ${N}` | 從第 10 個到第 N 個參數 | 238 | | `$*` 或 `$@` | 除了 `$0` 以外的所有參數 | 239 | | `$#` | 不包含 `$0`的參數數量 | 240 | | `$FUNCNAME` | function 名稱(只有一個變數在 function 內部) | 241 | 242 | 在下面的範例,命令列引數會是:`$0='./script.sh'`、`$1='foo'` 和 `$2='bar'`: 243 | 244 | ./script.sh foo bar 245 | 246 | 變數也可以有_預設值_。我們可以使用以下的語法來定義預設值: 247 | 248 | ```bash 249 | # 如果變數是空值,分配一個預設值。 250 | : ${VAR:='default'} 251 | : ${$1:='first'} 252 | # 或 253 | FOO=${FOO:-'default'} 254 | ``` 255 | 256 | # Shell expansion 257 | 258 | _Expansion_ 被劃分為 _token_ 後,會在命令列上執行。換句話說,這些 expansion(展開) 是一種機制來計算算術運算,以儲存命令的執行結果等等。 259 | 260 | 如果你有興趣的話,你可以閱讀[更多關於 shell expansions](https://www.gnu.org/software/bash/manual/bash.html#Shell-Expansions)。 261 | 262 | ## 括號展開 263 | 264 | 括號展開讓我們可以產生任意字串。它類似於_檔案名稱展開_。例如: 265 | 266 | ```bash 267 | echo beg{i,a,u}n # begin began begun 268 | ``` 269 | 270 | 括號展開也可以用在建立一個 loop iterated 的範圍。 271 | 272 | ```bash 273 | echo {0..5} # 0 1 2 3 4 5 274 | echo {00..8..2} # 00 02 04 06 08 275 | ``` 276 | 277 | ## 命令替代 278 | 279 | 命令替代允許我們對執行指令,並將指令的回傳值作為參數放進其他命令中,或是將回傳值指派到變數當中,當命令被 ``` `` ``` 或 `$()` 包住時,命令替代將會執行。例如,我們可以使用它如下所示: 280 | 281 | ```bash 282 | now=`date +%T` 283 | # 或 284 | now=$(date +%T) 285 | 286 | echo $now # 19:08:26 287 | ``` 288 | 289 | ## 算術代入展開 290 | 291 | 在 bash 裡我們可以很輕鬆的做到任何算術運算。但是表達式必須放在 `$(( ))` 裡面,算術代入展開的格式為: 292 | 293 | ```bash 294 | result=$(( ((10 + 5*3) - 7) / 2 )) 295 | echo $result # 9 296 | ``` 297 | 298 | 在算術代入展開內,變數不需要 `$` 前綴: 299 | 300 | ```bash 301 | x=4 302 | y=7 303 | echo $(( x + y )) # 11 304 | echo $(( ++x + y++ )) # 12 305 | echo $(( x + y )) # 13 306 | ``` 307 | 308 | ## 單引號和雙引號 309 | 310 | 單引號和雙引號有很重要的區別。在雙引號內的變數或是命令替代會被代入展開。在單引號內則不會。例如: 311 | 312 | ```bash 313 | echo "Your home: $HOME" # Your home: /Users/ 314 | echo 'Your home: $HOME' # Your home: $HOME 315 | ``` 316 | 317 | 區域變數和環境變數包含空格時,它們在引號內的展開要格外小心。隨便舉個例子,使用 `echo` 來印出一些使用者的輸入: 318 | 319 | ```bash 320 | INPUT="A string with strange whitespace." 321 | echo $INPUT # A string with strange whitespace. 322 | echo "$INPUT" # A string with strange whitespace. 323 | ``` 324 | 325 | 第一個 `echo` 調用了 5 個獨立的參數 — $INPUT 被拆成獨立的單字,`echo` 在每個單字之間列印一個空白字元。在第二種情況中,`echo` 調用了一個參數(整個 $INPUT,包含空白)。 326 | 327 | 現在來看看一個更嚴重的例子吧: 328 | 329 | ```bash 330 | FILE="Favorite Things.txt" 331 | cat $FILE # 嘗試印出兩個檔案的內容:`Favorite` 和 `Things.txt`。 332 | cat "$FILE" # 列印一個檔案的內容: `Favorite Things.txt`。 333 | ``` 334 | 335 | 在這個範例的問題可以透過把 FILE 重新命名成 `Favorite-Things.txt` 來解決,考慮輸入進來的環境變數、位置參數,或是命令的輸出(`find`、`cat` 等等)。如果輸入*可能*包含空格,請記得使用引號包起來。 336 | 337 | # 陣列 338 | 339 | 與大部分的程式語言一樣,在 bash 一個陣列是一個變數,讓你可以指向多個數值。在 Bash 中,陣列是從 0 開始,意思是陣列第一個元素的索引值是 0 。 340 | 341 | 當處理陣列時,我們應該要知道一個特殊的環境變數 `IFS`。**IFS** 或 **Input Field Separator**,是陣列中的元素之間的分隔符號。預設的 `IFS` 是一個空格 `IFS=' '`。 342 | 343 | ## 陣列宣告 344 | 345 | 在 Bash 你可以通過簡單地賦值給陣列變數的索引來建立陣列。 346 | 347 | ```bash 348 | fruits[0]=Apple 349 | fruits[1]=Pear 350 | fruits[2]=Plum 351 | ``` 352 | 353 | 也可以使用複合賦值建立陣列變數,像是: 354 | 355 | ```bash 356 | fruits=(Apple Pear Plum) 357 | ``` 358 | 359 | ## 陣列展開 360 | 361 | 個別陣列元素可以像其他變數一樣展開: 362 | 363 | ```bash 364 | echo ${fruits[1]} # Pear 365 | ``` 366 | 367 | 可以把 `*` 或 `@` 放在陣列索引值的地方將陣列展開: 368 | 369 | ```bash 370 | echo ${fruits[*]} # Apple Pear Plum 371 | echo ${fruits[@]} # Apple Pear Plum 372 | ``` 373 | 374 | 以上兩行有**很重要且微妙**的差別,假設陣列的元素值中包含空白: 375 | 376 | ```bash 377 | fruits[0]=Apple 378 | fruits[1]="Desert fig" 379 | fruits[2]=Plum 380 | ``` 381 | 382 | 我們想要將陣列中的元素隔行印出,所以我們試著用看看內建函數 `printf` : 383 | 384 | ```bash 385 | printf "+ %s\n" ${fruits[*]} 386 | # + Apple 387 | # + Desert 388 | # + fig 389 | # + Plum 390 | ``` 391 | 392 | 為什麼 `Desert` 和 `fig` 被分開了?讓我們嘗試使用雙引號括起來: 393 | 394 | ```bash 395 | printf "+ %s\n" "${fruits[*]}" 396 | # + Apple Desert fig Plum 397 | ``` 398 | 399 | 現在所有元素都被放在同一行了 - 這不是我們想要的!為了解決這個問題,接著試試 `${fruits[@]}` 吧: 400 | 401 | ```bash 402 | printf "+ %s\n" "${fruits[@]}" 403 | # + Apple 404 | # + Desert fig 405 | # + Plum 406 | ``` 407 | 408 | 在雙引號中,`${fruits[@]}` 將陣列每個元素視為成獨立的參數;保留了陣列元素中的空白。 409 | 410 | ## 切割陣列 411 | 412 | 除此之外,我們可以使用 _slice_ 運算符來取出陣列中某個部份: 413 | 414 | ```bash 415 | echo ${fruits[@]:0:2} # Apple Desert fig 416 | ``` 417 | 418 | 在上面的範例,`${fruits[@]}` expands 整個陣列的內容,而 `:0:2` 取出從索引為 0,長度為 2 的元素。 419 | _(譯者註:因此會從陣列位置 0 開始取 2 個元素)_ 420 | 421 | ## 把一個物件加入到陣列中 422 | 423 | 在一個陣列中新增元素也是相單簡單的。在這種情況下複合賦值特別有用。我們可以像這樣使用︰ 424 | _(譯者註:複合賦值-例如在 `a = a + 1` 中,就是將 a 的值帶入運算式中,再將值指派回原本的變數)_ 425 | 426 | ```bash 427 | fruits=(Orange "${fruits[@]}" Banana Cherry) 428 | echo ${fruits[@]} # Orange Apple Desert fig Plum Banana Cherry 429 | ``` 430 | 431 | 上面的範例中,`${fruits[@]}` 展開整個陣列的內容,並替換 fruits 然後帶入複合賦值,分配新的值到 `fruits` 陣列,修改原始的值。 432 | 433 | ## 把一個物件從陣列中移除 434 | 435 | 如果要從一個陣列中刪除一個元素,使用 `unset` 命令: 436 | 437 | ```bash 438 | unset fruits[0] 439 | echo ${fruits[@]} # Apple Desert fig Plum Banana Cherry 440 | ``` 441 | 442 | # Streams、pipes 和 lists 443 | 444 | Bash 在處理程式及輸出有非常強大的功能可以用。使用 streams 可以把程式的輸出導向到另一個程式的 input 或是一個檔案中,這讓我們可以方便的寫入任何我們想要的東西,或者是程式的紀錄檔。 445 | 446 | Pipes 給我們建立 conveyor 的機會和控制命令的執行。_(譯者註:converyor 為輸送的概念)_ 447 | 448 | 最重要的是我們瞭解如何使用這個強大和複雜的工具。 449 | 450 | ## Streams 451 | 452 | Bash 接收輸入並將輸出作為序列或字元的 **streams** 。這些 streams 可能會被重導向到檔案或其他 streams。 453 | 454 | 這裡有三個描述: 455 | 456 | | 代碼 | 描述符 | 描述 | 457 | | :--: | :--------: | :------------------- | 458 | | `0` | `stdin` | 標準輸入 | 459 | | `1` | `stdout` | 標準輸出 | 460 | | `2` | `stderr` | 錯誤輸出 | 461 | 462 | 重導讓我們可以控制命令的輸入來自哪裡,和輸出到哪去。在重導向 streams 時會用到以下運算子: 463 | 464 | | 運算子 | 描述 | 465 | | :------: | :------------------------------------------- | 466 | | `>` | 重新導向輸出 | 467 | | `&>` | 重新導向輸出和錯誤輸出 | 468 | | `&>>` | 以附加形式重新導向輸出和錯誤輸出 | 469 | | `<` | 重新導向輸入 | 470 | | `<<` | [Here 文件](http://tldp.org/LDP/abs/html/here-docs.html)語法 | 471 | | `<<<` | [Here 字串](http://www.tldp.org/LDP/abs/html/x17837.html) | 472 | 473 | 這裡有一些重導向的範例: 474 | 475 | ```bash 476 | # 將 ls 的輸出寫入到 list.txt。 477 | ls -l > list.txt 478 | 479 | # 將輸出附加到 list.txt。 480 | ls -a >> list.txt 481 | 482 | # 所有錯誤將會被寫入到 error.txt。 483 | grep da * 2> errors.txt 484 | 485 | # 從 errors.txt 讀取。 486 | less < errors.txt 487 | ``` 488 | 489 | ## Pipes 490 | 491 | 我們不只可以在檔案可以重導 streams,也可以在其他的程式中。**Pipes** 也能將輸出作為其他程式的輸入。 492 | 493 | 在下方的範例,`command1` 把輸出導向到 `command2`,然後 `command2` 再將輸出作為 `command3` 的輸入: 494 | 495 | command1 | command2 | command3 496 | 497 | 這樣的結構稱為 **pipelines**。 498 | 499 | 實際上,這可以用來在多個程式中依序處理資料。例如,`ls -l` 的輸出傳送到 `grep`,只列印出附檔名為 `.md` 的檔案,然後這個輸出最終傳到 `less`: 500 | 501 | ls -l | grep .md$ | less 502 | 503 | pipeline 的 exit status 通常是 pipeline 中最後一個命令的 exit status。shell 不會回傳狀態,直到所有在 pipeline 的命令都完成。如果任何在 pipeline 內的命令失敗,你應該設定 pipefail 選項: 504 | 505 | set -o pipefail 506 | 507 | ## 命令序列 508 | 509 | **命令序列**是由 `;`、`&`、`&&` 或 `||` 運算符分隔一個或多個 pipeline 序列。 510 | 511 | 如果一個命令是以 `&` 作為結尾,shell 會在 subshell 中非同步執行命令。換句話說,這個命令會在背景執行。 512 | 513 | 命令透過 `;` 隔開依序執行:一個接著一個指令執行直到所有命令完成。 514 | 515 | ```bash 516 | # command2 會在 command1 執行完後被執行。 517 | command1 ; command2 518 | 519 | # 等同於這個方式。 520 | command1 521 | command2 522 | ``` 523 | 524 | `&&` 和 `||` 分別稱為 _AND_ 和 _OR_ 序列。 525 | 526 | _AND 序列_ 看起來像是: 527 | 528 | ```bash 529 | # 只有在 command1 結束成功時(回傳 exit status 為 0 ),command2 才會被執行。 530 | command1 && command2 531 | ``` 532 | 533 | _OR 序列_ 的形式: 534 | 535 | ```bash 536 | # 只有在 command1 未成功完成時(回傳錯誤代碼 ),command2 才會被執行。 537 | command1 || command2 538 | ``` 539 | 540 | 一個 _AND_ 或 _OR_ 序列回傳的狀態碼,是最後一個執行命令的狀態碼。 541 | 542 | # 條件陳述式 543 | 544 | 與大部分的程式語言一樣,Bash 中的條件陳述讓我們決定是否執行某些程式。結果取決於在 `[[ ]]` 內的運算式。 545 | 546 | 條件表達式可以包含 `&&` 和 `||` 運算符,就是 _AND_ 和 _OR_。除此之外,這裡還有許多[其他方便的表達式](#primary-和組合表達式)。 547 | 548 | 有兩種不同條件陳述:`if` 和 `case`。 549 | 550 | ## Primary 和組合表達式 551 | 552 | 在 `[[ ]]`(或 `sh` 中的 `[ ]`)中的表達式稱為 **測試命令** 或 **primaries**。這些表達式幫助我們表明條件的結果。在下表中,我們使用 `[ ]`,因為它也可以在 `sh` 執行。這裡有關於在 bash 中,[雙引號和單引號的差別](http://serverfault.com/a/52050)的一些回答。 553 | 554 | **檢查檔案系統中的檔案或目錄的狀態:** 555 | 556 | | Primary | 涵義 | 557 | | :-----------: | :----------------------------------------------------------- | 558 | | `[ -e FILE ]` | 如果 `FILE` 存在(**e**xists),為 Ture | 559 | | `[ -f FILE ]` | 如果 `FILE` 存在且為一個普通的文件(**f**ile),為 True | 560 | | `[ -d FILE ]` | 如果 `FILE` 存在且為一個目錄(**d**irectory),為 True | 561 | | `[ -s FILE ]` | 如果 `FILE` 存在且不為空(**s**ize 大於 0),為 True | 562 | | `[ -r FILE ]` | 如果 `FILE` 存在且有讀取權限(**r**eadable),為 True | 563 | | `[ -w FILE ]` | 如果 `FILE` 存在且有寫入權限(**w**ritable),為 True | 564 | | `[ -x FILE ]` | 如果 `FILE` 存在且有執行權限(e**x**ecutable),為 True | 565 | | `[ -L FILE ]` | 如果 `FILE` 存在且為符號連結(**l**ink),為 True | 566 | | `[ FILE1 -nt FILE2 ]` | FILE1 比 FILE2 新(**n**ewer **t**han) | 567 | | `[ FILE1 -ot FILE2 ]` | FILE1 比 FILE2 舊(**o**lder **t**han) | 568 | 569 | **檢查字串狀態:** 570 | 571 | | Primary | 涵義 | 572 | | :------------: | :---------------------------------------------------------- | 573 | | `[ -z STR ]` | `STR` 為空(長度為 0,**z**ero) | 574 | | `[ -n STR ]` | `STR` 不為空 (長度不為 0,**n**on-zero) | 575 | | `[ STR1 == STR2 ]` | `STR1` 和 `STR2` 相等 | 576 | | `[ STR1 != STR2 ]` | `STR1` 和 `STR2` 不相等 | 577 | 578 | **二進制的算術運算符:** 579 | 580 | | Primary | 涵義 | 581 | | :-----------------: | :------------------------------------------------------- | 582 | | `[ ARG1 -eq ARG2 ]` | `ARG1` 和 `ARG2` 相等(**eq**ual) | 583 | | `[ ARG1 -ne ARG2 ]` | `ARG1` 和 `ARG2` 不相等(**n**ot **e**qual) | 584 | | `[ ARG1 -lt ARG2 ]` | `ARG1` 小於 `ARG2`(**l**ess **t**han) | 585 | | `[ ARG1 -le ARG2 ]` | `ARG1` 小於等於 `ARG2`(**l**ess than or **e**qual) | 586 | | `[ ARG1 -gt ARG2 ]` | `ARG1` 大於 `ARG2`(**g**reater **t**han) | 587 | | `[ ARG1 -ge ARG2 ]` | `ARG1` 大於等於 `ARG2`(**g**reater than or **e**qual) | 588 | 589 | 條件式也可以 **組合** 在一起使用: 590 | 591 | | 運算符 | 效果 | 592 | | :------------: | :----------------------------------------------------------------- | 593 | | `[ ! EXPR ]` | 如果 `EXPR` 為 false,則為 True | 594 | | `[ (EXPR) ]` | 回傳 `EXPR` 的值 | 595 | | `[ EXPR1 -a EXPR2 ]` | _AND_ 邏輯。如果 `EXPR1` **a**nd `EXPR2` 為 Ture,則為 True | 596 | | `[ EXPR1 -o EXPR2 ]` | _OR_ 邏輯。如果 `EXPR1` **o**r `EXPR2` 為 Ture,則為 True | 597 | 598 | 還有許多有用的 primariy 你可以在 [Bash man 網頁](http://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html)輕鬆的找到它們。 599 | 600 | ## 使用 `if` 陳述 601 | 602 | `if` 使用方式就像其他程式語言一樣。如果表達式括號內為 true,在 `then` 和 `fi` 之間的程式碼會被執行。`fi` 說明根據條件所執行的程式碼已經結束。 603 | 604 | ```bash 605 | # 單行 606 | if [[ 1 -eq 1 ]]; then echo "true"; fi 607 | 608 | # 多行 609 | if [[ 1 -eq 1 ]]; then 610 | echo "true" 611 | fi 612 | ``` 613 | 614 | 同樣的,我們可以使用 `if..else`,像是: 615 | 616 | ```bash 617 | # 單行 618 | if [[ 2 -ne 1 ]]; then echo "true"; else echo "false"; fi 619 | 620 | # 多行 621 | if [[ 2 -ne 1 ]]; then 622 | echo "true" 623 | else 624 | echo "false" 625 | fi 626 | ``` 627 | 628 | 有時候 `if..else` 不能滿足我們的需要。在這個情況下,別忘了 `if..elif..else`,使用起來也是很方便。 629 | 630 | 看看下面的例子︰ 631 | 632 | ```bash 633 | if [[ `uname` == "Adam" ]]; then 634 | echo "Do not eat an apple!" 635 | elif [[ `uname` == "Eva" ]]; then 636 | echo "Do not take an apple!" 637 | else 638 | echo "Apples are delicious!" 639 | fi 640 | ``` 641 | 642 | ## 使用一個 `case` 陳述 643 | 644 | 如果你面對的是好幾個不同情況,需要採取不同的行動,那麼使用 `case` 陳述或許會比巢狀化的 `if` 陳述來的有用。下方是使用 `case` 來處理更多複雜的條件: 645 | 646 | ```bash 647 | case "$extension" in 648 | "jpg"|"jpeg") 649 | echo "It's image with jpeg extension." 650 | ;; 651 | "png") 652 | echo "It's image with png extension." 653 | ;; 654 | "gif") 655 | echo "Oh, it's a giphy!" 656 | ;; 657 | *) 658 | echo "Woops! It's not image!" 659 | ;; 660 | esac 661 | ``` 662 | 663 | 每個 case 都會對應到一個條件。`|` 符號是被用來分離多個條件,而 `)` 運算符是條件的結尾。第一個符合的命令會被執行。`*` 代表不對應以上所給的任一條件。每個命令區塊之間需要透過 `;;` 隔開。 664 | 665 | # 迴圈 666 | 667 | 這裡我們不必感到驚訝。與任何程式語言一樣,在 bash 之中,如果判斷式結果為 true,就會迭代迴圈。 668 | 669 | 在 Bash 有四種類型的迴圈:`for`、`while`、`until` 和 `select`。 670 | 671 | ## `for` 迴圈 672 | 673 | `for` 非常相似於 C 的 `for` 迴圈。它看起來像是: 674 | 675 | ```bash 676 | for arg in elem1 elem2 ... elemN 677 | do 678 | # statements 679 | done 680 | ``` 681 | 682 | 在每個迴圈中,`arg` 會依序被賦予 `elem1` 到 `elemN` 的值。值可能是萬用字元或[括號展開](#括號展開)。 683 | 684 | 當然,我們也可以將 `for` 迴圈寫成一行,但在這個情況我們需要在 `do` 之前放一個分號,像是這樣: 685 | 686 | ```bash 687 | for i in {1..5}; do echo $i; done 688 | ``` 689 | 690 | 順便一提,如果你覺得 `for..in..do` 看起來很奇怪,你也可以撰寫 C 語言風格的 `for` 迴圈,像是: 691 | 692 | ```bash 693 | for (( i = 0; i < 10; i++ )); do 694 | echo $i 695 | done 696 | ``` 697 | 698 | 當我們想要將目錄下每個文件做相同的操作時,`for` 是相當方便的。例如,如果我們需要移動所有的 `*.bash` 檔到 `script` 資料夾,並給予它們可執行的權限,我們的 script 可以像是這樣: 699 | 700 | ```bash 701 | #!/bin/bash 702 | 703 | for FILE in $HOME/*.bash; do 704 | mv "$FILE" "${HOME}/scripts" 705 | chmod +x "${HOME}/scripts/${FILE}" 706 | done 707 | ``` 708 | 709 | ## `while` 迴圈 710 | 711 | 只要該條件為 _true_,`while` 迴圈測試一個條件,並遍歷序列的命令。被檢測的條件跟 `if.. then` 中使用的 [primary](#primary-和組合表達式) 並沒有差別。所以 `while` 迴圈看起來會像是: 712 | 713 | ```bash 714 | while [[ condition ]] 715 | do 716 | # statements 717 | done 718 | ``` 719 | 720 | 就像 `for` 迴圈一樣,如果我們想要將 `do` 和條件寫成一行的話,我們必須在 `do` 之前加上分號。 721 | 722 | 一個處理範例如下所示: 723 | 724 | ```bash 725 | #!/bin/bash 726 | 727 | # 0 到 9 每個數字的平方。 728 | x=0 729 | while [[ $x -lt 10 ]]; do # x 小於 10。 730 | echo $(( x * x )) 731 | x=$(( x + 1 )) # x 加 1。 732 | done 733 | ``` 734 | 735 | ## `until` 迴圈 736 | 737 | `until` 迴圈和 `while` 迴圈完全相反的。它會像 `while` 一樣檢查條件,迴圈持續到表達式為 False 停止: 738 | 739 | ```bash 740 | until [[ condition ]]; do 741 | #statements 742 | done 743 | ``` 744 | 745 | ## `select` 迴圈 746 | 747 | `select` 迴圈幫助我們組織一個使用者功能表。它和 `for` 迴圈語法幾乎相同: 748 | 749 | ```bash 750 | select answer in elem1 elem2 ... elemN 751 | do 752 | # statements 753 | done 754 | ``` 755 | 756 | `select` 會將 `elem1..elemN` 跟隨著序號列印出來提示使用者。然後將選單的提示文字設定在 `PS3` 當中。在上面的例子當中,使用者的輸入會存於 `answer` 中。如果 `answer` 是介於 `1..N` 的數字,那敘述句就會被執行且 `select` 會因為 do-loop 的關係而循環,因此我們應該使用 `break` 來避免無窮迴圈。 757 | 758 | 一個處理範例如下所示: 759 | 760 | ```bash 761 | #!/bin/bash 762 | 763 | PS3="Choose the package manager: " 764 | select ITEM in bower npm gem pip 765 | do 766 | echo -n "Enter the package name: " && read PACKAGE 767 | case $ITEM in 768 | bower) bower install $PACKAGE ;; 769 | npm) npm install $PACKAGE ;; 770 | gem) gem install $PACKAGE ;; 771 | pip) pip install $PACKAGE ;; 772 | esac 773 | break # 避免無窮迴圈。 774 | done 775 | ``` 776 | 777 | 在這個範例中,詢問使用者想要使用哪套 package manager 安裝套件,並詢問我們需要安裝什麼套件,最後會執行安裝: 778 | 779 | 如果我們執行,我們可以得到: 780 | 781 | ``` 782 | $ ./my_script 783 | 1) bower 784 | 2) npm 785 | 3) gem 786 | 4) pip 787 | Choose the package manager: 2 788 | Enter the package name: bash-handbook 789 | 790 | ``` 791 | 792 | ## 迴圈控制 793 | 794 | 在某些情況下,我們需要在正常結束前或跳過某次迭代來結束迴圈。在這個情況下,我們可以使用 shell 內建的 `break` 和 `continue` 語句。這兩個語法都可以在前述的每一種迴圈執行。 795 | 796 | `break` 語句被用於在迴圈結束之前,退出目前迴圈。我們之前已經看過了。 797 | 798 | `continue` 語句跳過一個迭代。我們可以像這樣使用它: 799 | 800 | ```bash 801 | for (( i = 0; i < 10; i++ )); do 802 | if [[ $(( i % 2 )) -eq 0 ]]; then continue; fi 803 | echo $i 804 | done 805 | ``` 806 | 807 | 如果我們執行上方的範例,我們可以列印出 0 到 9 間的奇數。 808 | 809 | # 函式 810 | 811 | 在 script 中,就像任何其他程式語言一樣,我們有能力定義 function 和呼叫 function。在 Bash 中的 function 是一個程式碼區塊,但是有些不同。 812 | 813 | 在 Bash 中的 function 就是將一堆指令包裝成一個 name,而這個 name 就是 function name,呼叫一個 function 就像在其他程式語言一樣,你只要執行 function 的名稱,function 就會被 _調用_。 814 | 815 | 我們可以用這種方式宣告我們的 function: 816 | 817 | ```bash 818 | my_func () { 819 | # statements 820 | } 821 | 822 | my_func # 呼叫 my_func。 823 | ``` 824 | 825 | 我們在調用 function 前,必須要先宣告。 826 | 827 | Function 可以接受參數並回傳一個結果 — exit code。在 function 內,與[非互動模式](#非互動模式)下的 script 參數處理方式相同 — 使用[位置參數](#位置參數)。結果代碼可以使用 `return` 命令來 _回傳_。 828 | 829 | 以下的 function 需要一個名稱並回傳 `0`,表示成功執行。 830 | 831 | ```bash 832 | # 帶參數的 function。 833 | greeting () { 834 | if [[ -n $1 ]]; then 835 | echo "Hello, $1!" 836 | else 837 | echo "Hello, unknown!" 838 | fi 839 | return 0 840 | } 841 | 842 | greeting Denys # Hello, Denys! 843 | greeting # Hello, unknown! 844 | ``` 845 | 846 | 我們已經討論過 [exit codes](#exit-codes)。`return` 命令不帶任何最後一個執行命令 exit code 的回傳參數。上面的範例,`return 0` 會回傳一個成功的 exit code `0`。 847 | 848 | ## Debug 849 | 850 | shell 提供了 debug script 的工具。如果我們想要在 debug 模式執行 script,我們在 script 的 shebang 使用一個特別的選項: 851 | 852 | ```bash 853 | #!/bin/bash options 854 | ``` 855 | 856 | 這個選項是改變 shell 行為的設定。下面是一些可能對你有幫助的選項清單: 857 | 858 | | 簡寫 | 名稱 | 描述 | 859 | | :---: | :---------- | :-------------------------------------------------------- | 860 | | `-f` | noglob | 禁止檔案名 expansion(globbing)      | 861 | | `-i` | interactive | Script 執行在_互動_模式 | 862 | | `-n` | noexec | 讀取命令,但不執行它們(確認語法是否正確) | 863 | | | pipefail | 如果任何命令失敗使 pipeline 失敗,不只是最後一個命令失敗 | 864 | | `-t` | — | 在第一個命令完成後退出 | 865 | | `-v` | verbose | 在執行前,列印每個命令到 `stderr` | 866 | | `-x` | xtrace | 在執行前,列印每個命令和他的參數到 `stderr` | 867 | 868 | 例如,我們有個 script 有 `-x` 選項像是: 869 | 870 | ```bash 871 | #!/bin/bash -x 872 | 873 | for (( i = 0; i < 3; i++ )); do 874 | echo $i 875 | done 876 | ``` 877 | 878 | 這將會列印變數的值到 `stdout` 以及其他有用的資訊: 879 | 880 | ``` 881 | $ ./my_script 882 | + (( i = 0 )) 883 | + (( i < 3 )) 884 | + echo 0 885 | 0 886 | + (( i++ )) 887 | + (( i < 3 )) 888 | + echo 1 889 | 1 890 | + (( i++ )) 891 | + (( i < 3 )) 892 | + echo 2 893 | 2 894 | + (( i++ )) 895 | + (( i < 3 )) 896 | ``` 897 | 898 | 有時候我們需要 debug script 某個部份。在這種情況下使用 `set` 命令是很方便的,使用 `-` 啟用選項,`+` 停用選項。 899 | 900 | ```bash 901 | #!/bin/bash 902 | 903 | echo "xtrace is turned off" 904 | set -x 905 | echo "xtrace is enabled" 906 | set +x 907 | echo "xtrace is turned off again" 908 | ``` 909 | 910 | # 後記 911 | 912 | 我希望這麼小手冊會是有趣並有幫助的。老實說,我撰寫這個手冊是為了幫助自己不忘記這些基本的 bash。我嘗試透過簡單但有意義的方式,希望你們會喜歡。 913 | 914 | 這本手冊講述我自己與 Bash 的經驗。它並不全面,所以如果你還需要更多資訊的話,請執行 `man bash` 並從那裡開始。 915 | 916 | 非常歡迎你的貢獻,任何指正或問題我都非常感激。這些都可以透過建立一個新 [issue](https://github.com/denysdovhan/bash-handbook/issues) 來進行。 917 | 918 | 感謝你閱讀這本手冊! 919 | 920 | # 想要了解更多嗎? 921 | 922 | 這裡有一些涵蓋 Bash 的相關資料: 923 | 924 | * 在許多環境中,你可以執行 Bash 中的 `man bash`,藉由系統並顯示 Bash 相關的幫助資訊。有關更多 `man` 命令的相關資訊,請拜訪在 [The Linux Information Project](http://www.linfo.org/) 的 ["The man Command"](http://www.linfo.org/man.html) 網頁查看。 925 | * ["Bourne-Again SHell manual"](https://www.gnu.org/software/bash/manual/) 有許多格式,包含 HTML、Info、TeX、PDF、和 Texinfo。在 。在 2016/01,包括 4.3 版本,最後更新時間 2015/02/02。 926 | 927 | # 其他資源 928 | 929 | * [awesome-bash](https://github.com/awesome-lists/awesome-bash) 是一個 Bash script 和其他資源的列表。 930 | * [awesome-shell](https://github.com/alebcay/awesome-shell) 是另一個 Shell 及其他資源的列表。 931 | * [bash-it](https://github.com/Bash-it/bash-it) 為你日常工作使用、 開發和維護 shell script 和自訂命令提供了可靠的框架。 932 | * [dotfiles.github.io](http://dotfiles.github.io/) 是一個很棒的指南,蒐集了多個 dotfiles 及可用在 bash 及其他 shell 的開發框架。 933 | * [learnyoubash](https://github.com/denysdovhan/learnyoubash) 幫助你撰寫你的第一個 bash script。 934 | * [shellcheck](https://github.com/koalaman/shellcheck) 是一個 shell script 的靜態分析工具。你可以從在 [www.shellcheck.net](http://www.shellcheck.net/) 網頁上或從指令來執行。安裝說明在 [koalaman/shellcheck](https://github.com/koalaman/shellcheck) 的 Github repo 頁面。 935 | 936 | 最後,Stack Overflow 有許多[標記為 bash](https://stackoverflow.com/questions/tagged/bash) 的問題,當你在卡住時,你可以從那些地方學習或是提問。 937 | 938 | # License 939 | 940 | [![CC 4.0][cc-image]][cc-url] © [Denys Dovhan](http://denysdovhan.com) 941 | 942 | [cc-url]: http://creativecommons.org/licenses/by/4.0/ 943 | [cc-image]: https://i.creativecommons.org/l/by/4.0/80x15.png 944 | --------------------------------------------------------------------------------