In Previous article, after sudo rm -rf / *
, implement __selection sort __ and enter the numeric string entered by the user. I did something like sorting and writing to a file.
I couldn't use the ps
command, so I barely touched on process-related things. So this time I would like to implement the disappeared ps
command only from the __bash built-in command __. Properly this time also sudo rm -rf / *
from the beginning.
Basically, I will introduce the commands and syntax that appear, but I will not deal with the ones introduced in the previous article such as function and variable definitions, arrays, if statements, and for statements in detail. Please refer to Previous article. Let's go.
・ References O'Reilly Japan Introduction bash
・ Execution environment Raspberry Pi 4 Model B+ Raspbian Buster Lite 2020-02-13-raspbian-buster-lite.img
There are many commands that have disappeared with sudo rm -rf / *
, but ls
and cat
are often used and will be redefined. Also, the complement is strange, so I'll fix it.
You can use ʻecho *as an alias for
ls (that is, ʻalias ls =" echo * "
), but since it's a big deal, the ls
command that displays the directory in blue and the others in white make.
To do this, use the test
command (same for the [
command). This command uses the __file attribute operator __ to determine the condition. For example, the file attribute operator -a
indicates" whether the file exists ". Let's look at a concrete example.
In /
, the proc
directory exists and returns 0 as the exit status, but the bin
directory no longer exists and returns 1.
Use this to create the ls
command. -d
is a file attribute operator that indicates whether a directory exists.
.bash
function ls
{
for i in *
do
if [ -d $i ]
then
echo -ne "\e[34m$i\e[m\t" #Directory is blue
else
echo -ne "$i\t" #Other than that, it is white
fi
done
echo #Echo for line breaks
}
I referred to the article here to color with ʻecho. Also, the tab character (
\ t) is inserted between the file names. Let's actually use this
lsin a suitable directory. <img width="718" alt="スクリーンショット 2020-03-23 20.56.34.png " src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/2bf63ea8-0be9-56a1-ce0a-a9fc99e7cafb.png "> The line breaks are a little strange unlike the normal
ls, but let's do it this time. I think that you can output a little more beautifully if you use the shell variable
COLUMNS` that represents the number of display columns of the terminal to separate cases.
Use a while statement for this. The syntax is:
while <Conditional expression>
do
<A series of sentences>
done
As with the if statement, the exit status of the command is used for <conditional expression>
. For example:
.bash
a=0
while [ $a -ne 3 ]
do
echo $a
let a++
done
-ne
means not equal. When the shell variable ʻais other than 3,
[$ a -ne 3] returns an exit status of 0 and loops, and when ʻa
becomes 3, it returns 1 and exits the loop. The built-in command let
evaluates the arithmetic operator and assigns the result to a variable. In the above example we just increment ʻa`.
When you actually try it, it will be as follows. When ʻa` becomes 3, you can see that the command has finished properly by exiting the loop.
Now let's implement the cat
command using a while statement. For simplicity, we will only implement the ability to take one argument and display its contents.
.bash
function cat
{
while read val
do
echo $val
done
} < $1
The read
command is a command that assigns a value to a shell variable. In this example, the contents of the file are assigned to the shell variable val
line by line and displayed with ʻecho. When the file specified by
$ 1 is empty, the
read` command returns 1 as the exit status, so you can get out of the loop properly.
Also, sudo rm -rf / *
may not be able to complete well with tab.
↓ tab input
I'm getting an error I've never seen. You can see what happens to tab completion in the cat
command by using the built-in command complete
.
This means that the completion of the cat
command is determined by the function _longopt
. You can see the details of this function in declare -f _longopt
, but I won't go into it here. The point is that this function is __ not working properly due to the effect of sudo rm -rf / *
.
This time, let's simply do __complement by file name __. Specifically, write as follows using the -f
option that completes from a normal file name.
complete -f cat
As mentioned in Previous article, file name completion can be done with ʻESC + / , but now it can also be done with tab. By the way, let's make tab available for the
cdcommand as well. With the
-d` option, completion is done from __directory name __.
complete -d cd
Now let's actually create the ps
command. Start with a description of the / proc
directory that contains process information.
On Linux, there is a / proc
directory that contains process information. This is a pseudo directory different from a normal directory and will not disappear with sudo rm -rf / *
.
When you actually look at it, it looks like this. The directory consisting only of the upper numbers contains the process information corresponding to the process ID (PID).
By the way, this / proc
directory exists on Linux, but on pure UNIX (for example, BSD-based FreeBSD and Mac OS, System V-based Solaris, etc.) __ does not exist __, or even if it exists __ Please note that the contents may be different __.
Now let's take a look at the directory of the process ID of this login shell (that is, bash). You can find the process ID of the current shell with ʻecho $$. <img width="718" alt="スクリーンショット 2020-03-23 21.00.24.png " src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/a4c0e28f-f0c6-07db-50c1-5f3dc653e7af.png "> There is a file called
stat` in it that contains information about the process.
Let's start from the beginning.
--The first is the process ID, which has a value of 768
.
――The second is the executable file name enclosed in parentheses, which is (bash)
.
--The third is R
in the process state. R
stands for Running.
I can't cover all of them, so see man proc
for details.
The ps
command captures these values and displays them on the screen. However, the default of the ps
command is too few processes to display, so I would like to create a ps a
command with the ʻa option, that is, a
ps` command to display all processes with terminals.
First, create a read_stat
function that takes this stat
file as an argument and assigns it to a shell variable.
.bash
function read_stat
{
read -a values
pid=${values[0]}
comm=${values[1]}
state=${values[2]}
tty_nr=${values[6]}
time=$(( ${values[13]} / 100 ))
} < $1
First, use the -a
option of the read
command to put each value of stat
into an array called values
. Below is a description of shell variables.
--pid
process ID
--comm
Executable file name
--state
Process state
--tty_nr
The name of the terminal to which the process is connected
--time
Execution time (unit is seconds)
I will actually run it. You can see that the value is entered properly.
I can display the basic information with this, but the terminal name cannot be displayed well. That's because you have to convert the number in tty_nr
(34816 in the above example) to a string like tty1
or pts / 0
. Let's talk a bit about device files here.
In UNIX-based OS, hardware such as HDD, USB memory, and terminal can be handled as files. Such files are called device files. / Dev / null
to discard the output and / dev / random
to generate a random string are also device files that you may have used. There are two types of device files: block type and character type. The former is a file for operating __disk devices, and the latter is a file for handling other files.
Device files are managed using __major number __ and __minor number __. Let's check the actual Linux documentation.
https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt
For example, "block type device file major number 1" is assigned to "RAM disk". The minor number indicates the number of the RAM disk.
The terminal displayed by the ps
command is basically a __TTY device __ with major number 4 or a __Unix98 PTY slave __ with major number 136. The TTY device is the screen that is directly connected to the __Raspberry Pi, and the Unix98 PTY slave is a type of pseudo terminal. The point is the screen when connecting with __SSH.
Now let's get back to tty_nr
. The meaning of this numeric string is written in man proc
, so I will quote it.
(7) tty_nr %d The controlling terminal of the process. (The minor device number is contained in the combination of bits 31 to 20 and 7 to 0; the major device number is in bits 15 to 8.)
The 31st to 20th and 7th to 0th bits of the tty_nr
numeric string are minor numbers, and the 15th to 8th bits are major numbers.
Use these to create a function that displays the terminal name in the shell variable tty
.
.bash
function get_tty
{
major_num=$((tty_nr>>8))
minor_num=$((tty_nr&255))
if [ $major_num -eq 4 ]
then
tty=tty${minor_num}
elif [ $major_num -eq 136 ]
then
tty=pts/${minor_num}
else
tty=???
fi
}
The major number is extracted by using 8-bit left shift (tty_nr >> 8
), and the minor number is extracted from the logical product (tty_nr & 255
) with 255 which is 11111111 in binary. Actually, it is not accurate unless you use 31 to 20 bits, but this time there is no problem, so I made it this way.
If the major number is 4, it is a normal terminal (tty1
, etc.), if it is 136, it is a pseudo terminal (pts / 0
, etc.), otherwise it is unknown (???
), and the shell variable tty is used. Assigned to
.
Let's actually use it. Since this experiment was done by connecting from mac with SSH, the pseudo terminal was displayed properly.
Create a ps
command using the read_stat
and get_tty
functions created so far.
.bash
function ps {
echo -e "PID\tTTY\tSTATE\tTIME\tCMD"
for stat in /proc/[1-9]*/stat #Consider a directory that starts with a number and has stat
do
read_stat $stat
if [ $tty_nr -ne 0 ] #Show only processes with terminals
then
get_tty #get tty
echo -e "${pid}\t${tty}\t${state}\t${time}\t${comm:1:-1}" #Of the value of comm()Remove
fi
done
}
Here, the shell variable comm
has a string in parentheses, so I remove it and output ($ {comm: 1: -1}
).
Let's actually use it. It was output properly. This time, I also found that bash is running on the monitor that is directly connected to the Raspberry Pi, and its PID is 614.
I think you can realize that you can do anything unexpectedly with just the built-in commands of bash. If I have time, I'd like to talk about process management (job concepts, kill
commands, signals, etc.).