sudo rm -rf / * Recreate ps command with wreckage

1.First of all

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. スクリーンショット 2020-03-23 17.57.34.png 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

2 Preparation

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.

2.1 ls command

You can use ʻecho *as an alias forls (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. スクリーンショット 2020-03-23 18.28.46.png 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 normalls, 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.

2.2 cat command

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. スクリーンショット 2020-03-23 23.32.17.png 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.

2.3 Completion

Also, sudo rm -rf / * may not be able to complete well with tab. スクリーンショット 2020-03-23 18.01.52.png ↓ tab input スクリーンショット 2020-03-23 18.01.44.png 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. スクリーンショット 2020-03-24 13.30.56.png 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

3 Creating a ps command

Now let's actually create the ps command. Start with a description of the / proc directory that contains process information.

3.1 / proc directory

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. スクリーンショット 2020-03-23 20.57.53.png 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. スクリーンショット 2020-03-23 21.01.02.png 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. スクリーンショット 2020-03-23 21.29.16.png 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.

3.2 Device files

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

スクリーンショット 2020-03-23 22.59.37.png

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. スクリーンショット 2020-03-23 23.13.15.png Since this experiment was done by connecting from mac with SSH, the pseudo terminal was displayed properly.

3.3 Creating a ps command

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. スクリーンショット 2020-03-23 23.11.26.png 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.

At the end

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.).

Recommended Posts

sudo rm -rf / * Recreate ps command with wreckage
ps command "wchan"