adnan360 789a93566a Add bash notes further reading | 2 年 前 | |
---|---|---|
.. | ||
README.md | 2 年 前 | |
input-stdin.sh | 2 年 前 |
These are just Unix/Linux shell scripting notes. Even though it's named "scripting" notes, there are many things listed here which can be used for using the terminal better. It is mostly written for bash/ksh. But at least some, possibly all, can be applied to /bin/sh
as well.
NOTE: Since these are just personal notes it might contain some mistakes. If you find any, please post an Issue or a PR. Research yourself before adapting anything.
a. Shell Basics b. Running things c. Handling outputs d. Variables
You can run shell commands on a terminal application, such as, xterm, LXTerminal, sakura, st, GNOME Terminal and even on Termux (Android). You can also run these on a TTY (you can access a TTY by pressing Ctrl+Alt+F1 through F8 and logging in).
If you login as a normal user on TTY or just open a GUI terminal, you may see a $
sign on your prompt. If you login as root, you may see a #
. Even though there are exceptions, this is fairly common in most systems.
Following this convention, commands that are supposed to be run as normal user starts with a $
at the beginning of the line. Otherwise if it is meant for the root user to run, it starts with a #
at the beginning:
$ echo test
test
$ whoami
john
$ su
# whoami
root
As you can see, lines that do not have either $
or #
are output lines. When we switched the user to root, the line is shown with #
at the beginning. This is a not a hard-set rule and anybody can invent anything else. But it is more or less followed by everyone.
To know which user you are currently logged in as, run whoami
and it will tell you. To get out of root user mode, run exit
or press Ctrl+D.
To know which shell you are on, run either of these:
$ echo $0
ksh
$ echo $SHELL
/bin/ksh
In my experience $0
is more reliable, since $SHELL
returns the initial shell even if subsequent shells are accessed.
This is just an example. If you are running something else, like bash, zsh etc. you will get output according to that.
From a terminal shell, it's as easy as using curly braces ({...}
). If you want to run multiple commands:
{
and enter}
and enterThis will execute all the commands you entered.
There are other ways to do this with advantages and disadvantages.
There is a way to save the commands on a file. The filename should be preferably with .sh
extension, but anything else even without extension would work as well.
There are 2 ways of running the file:
bash test.sh
.#!/usr/bin/env bash
as the first line, the script can be made executable with chmod +x test.sh
then run with ./test.sh
or /path/to/test.sh
.Saving to a file makes it easier if you want to run the script again in future. You won't have to type each command again in order to run it.
Usually when we run something from a terminal it occupies the terminal. We cannot do anything else until the program terminates. To run something and continue to use the terminal this can be used:
someprogram &
But this has some problems. It lets us use the terminal for other things but:
So there are some ways to deal with it: nohup
or disown
In systems where there nohup
exists, it can be used:
nohup someprogram
It may print something like "sending output to nohup.out" and nothing else, so the terminal can be freely used for anything else or even exited.
Although it creates a nohup.out file with the program output on current directory or if not possible, on $HOME
.
If it is available, you can also use disown
:
someprogram & disown
Even though it works, it outputs everything on originating terminal. So:
someprogram >/dev/null 2>&1 & disown
>
without any number (e.g. 1>
) means 1. &1
is referring to 1>
which is /dev/null
here. So these three does the same thing:
someprogram 1>/dev/null 2>/dev/null & disown
someprogram >/dev/null 2>/dev/null & disown
someprogram >/dev/null 2>&1 & disown
To avoid repitition we use &1
.
Further explanation of redirection methods can be found here. &>/dev/null
might not work everywhere except bash.
On script files, we can safely ignore nohup
or disown
or similar solutions.
To run something that you want to kill later, you can capture the process id (PID) so that a kill signal can be sent to it. $!
is a special variable that returns the PID of last background run job:
$ for i in `seq 1 10`; do echo $i; sleep 10; done &
$ PID=$!
$ kill -3 $PID
Here, we are running for loop that prints a number from 1 to 10, with 10 seconds break in between. It will take more than a minute to finish this. If immediately after running this, we run the following 2 commands, it will kill the for process and stop the counting.
Some programs offer specifying a PID file for this exact reason. PID file is just a regular file that contains the PID as the file content. e.g. tor
has a PidFile
option.
Each program is meant to return exit code of 0
(zero) when it runs and ends successfully. Anything other than that means there was something wrong.
$?
is a special variable that contains the exit code for last run command.
$ ls /tmp
$ echo $?
0
$ ls nonexistingfile.txt
ls: nonexistingfile.txt: No such file or directory
$ echo $?
1
The last ls
command had an error, so it returned 1
. We can use this to show messages, take actions and what not:
#!/usr/bin/env bash
ls /root
if [ "$?" = '0' ]; then
echo 'accessing root files was successful'
else
echo 'accessing root files has failed!'
fi
You can save the above on a test.sh
file and run bash test.sh
. On an adequately secure system, normal users shouldn't be able to ls
root files. Only root user should. So, depending on which user you run this as, it will either show a success or a failure message.
Above can be shortened with:
#!/usr/bin/env bash
if ls /root; then
echo 'accessing root files was successful'
else
echo 'accessing root files has failed!'
fi
To keep things simple, the output wasn't suppressed. ls /root >/dev/null 2>&1
can be used instead of ls /root
to suppress output.
Output produced by a program can be put in a file by:
someprogram > output.txt
No matter what someprogram
prints out, it will be saved to output.txt
. e.g.
ls -la > listoffiles.txt
After running this listoffiles.txt
will have the list of files and directories in listoffiles.txt
.
However, >
will delete previous content of the output file. If you want to keep the existing content of the output file and just add to it, use >>
instead of >
. e.g.
date >> date.txt
date >> date.txt
date >> date.txt
After running the first one it will add a line to date.txt
, and after running second one it will add to it. These 3 commands will make the date.txt
file have 3 dates, each on it's own line. This means it didn't delete previous content.
There is another way to add or append something to a file, and that's tee
.
echo test | tee -a test.txt
Each time you execute this command, a line "test" will be added to test.txt
.
tee
is extremely helpful when you want to add to a file in a priviledged location. e.g.
echo 'someconfig = 1' | sudo tee -a /etc/sysctl/someconfig.conf
tee
can be run without -a
to replace existing content. e.g.
echo test | tee test.txt
Replaces existing content and places "test" to the test.txt
.
We can store the output of a program in a variable like this:
$ mydate=$(date)
$ echo $mydate
Tue Mar 22 20:04:42 +00 2022
With $(date)
we are capturing the output of date
command into a variable. We are then setting that variable value to mydate
variable.
Another example:
$ usrlist=$(ls /usr)
$ echo "My /usr contains:\n$usrlist"
My /usr contains:
X11R6
bin
distfiles
games
include
lib
...
$(...)
and `...`
does the same thing. You can try the above example with usrlist=`ls /usr`
and it should do the same thing. For clarity and consistency, use either one in a project and stick with it. I like $(...)
because it's easier to read and find. But the other one is ok too if you want to use it.
Keeping something in a variable is as easy as doing:
a=5
Now if you do a echo $a
it should return "5".
This is a small example so it might not need quotes. With values containing spaces it will need a quote. e.g.
name='John Doe'
Now echo $name
will work. But it is a good practice to surround variables with double quotes. A double quote takes care of whether the string is one word or multiple words or even multiple lines (especially when using on if
conditions). So, echo "$name"
would be perfect.
Using $
to refer to a variable only works inside a double quote. It does not work under single quotes.
$ echo "$name"
John Doe
$ echo '$name'
$name
Most of the time double quote is what we want, unless we need to print the variable name specifically, e.g. in an error message containing the variable name.
To add something to a variable, double quote is the way to go. e.g.
echo "My name is $name"
This is ok for most cases if you don't need any letters, underscores (_) directly after the name. But if you do need to place one, you'd have to be specific of the variable name:
$ echo "My name is ${name}_tnrkdnmk"
My name is John Doe_tnrkdnmk
If you add ${...}
around the variable name, you can add anything after it.
Adding one string with another (aka concatenating) is possible:
$ firstname=John
$ lastname=Doe
$ echo "$firstname $lastname"
John Doe
$ echo $firstname $lastname
John Doe
$ echo $firstname$lastname
JohnDoe
$ echo "some""text"
sometext
$ echo $firstname", the neighbor"
John, the neighbor
Strings with multiple lines are as easy as:
~ $ multi="one line\nanother line"
~ $ echo $multi
one line
another line
\n
inside double quotes makes a new line.
Looping through multiple lines is easy:
$ echo "$multi" | while IFS= read -r line; do echo "Reading line:" $line; done
Reading line: one line
Reading line: another line