Hello! I'm @shinooooo, the mentor in charge of the 24th day of DMM WEBCAMP Advent Calendar 2020.
One day left until Christmas! How do you all spend your time? I think each person has a different schedule, but first of all, I would like to thank you for taking the time to read this article on your precious Christmas Eve. Thank you!
In this article, I wrote an explanation of the ls command implemented in Ruby and the knowledge gained through the implementation. I myself am learning Ruby, so I would appreciate any advice on the content and code of the article.
If you are learning programming, I think it is a command that you have executed once.
The function of ls
is according to the site that translates Linux related documents into Japanese.
ls, dir, vdir --list the contents of the directory
Is written.
When you actually run it,
$ ls
hoge huga index.html #The files in the current directory are output.
I think that such a display will come out.
This time, I implemented this ls command in Ruby and
$ ruby ls.rb
hoge huga index.html
The goal is to issue.
The specifications of ls.rb
implemented this time are as follows.
--Implemented functionality equivalent to ls
, ls -l
, ls -a
, ls -la
--Due to the use of the library'optparse', you cannot specify multiple options at once like -la
. If you want to combine, you need to enter -l -a
.
--Display of function called ACL (explanation is omitted) is not implemented
--Since the actual ls
source code is not referred to, the algorithm and function names may be different.
You can't just write code to imitate it, so let's analyze the output of ls
.
It is easier to understand if you prepare the directory and files, so execute the following command.
# directory1~directory10 and file1~Create file10 in the test directory.
$ mkdir test/directory{1..10} && touch test/file{1..10}
Then run ls test
to see the output.
$ ls test
directory1 directory3 directory6 directory9 file2 file5 file8
directory10 directory4 directory7 file1 file3 file6 file9
directory2 directory5 directory8 file10 file4 file7
Speaking of course, it is natural, but they are lined up neatly.
It seems that just outputting to the dark clouds will not produce a neat output result like ls
, so I will aim for completion while identifying each issue one by one.
The file names output by ls
are arranged in ascending order in the following order.
The output of "Number of files displayed on one line" and "Number of lines" changes slightly depending on "Number of files", "Terminal screen size", and "File name length", but [ The output of ls test
confirmed by Display order was as follows.
Displayed order
File with the youngest name File with the fourth youngest name File with the seventh youngest name...File with the 19th youngest name
File with the second youngest name File with the fifth youngest name ︙...File with the 20th youngest name
File with the third youngest name File with the sixth youngest name ︙
Line by line name is 1,4,7,10,13,16,19th youngest file
, name is 2,5,8,11,14,17,20th youngest file
,name is It seems that the output of
ls can be reproduced by outputting the youngest file
in order of 3,6,9,12,15,18.
The Dir class used to get the list of files in the directory will pass the list of files as an array. This time, we will assign this to a variable called files and handle it.
However, since the contents of the received array were not in ascending order, sort them in ascending order with the sort
method of Array class.
sort method
files = ["file2", "file3", "file1"]
files.sort #=> ["file1", "file2", "file3"]
Array subscripts start at 0.
By sorting in ascending order, the youngest file name in files can be obtained by setting files [0]
.
Outputting the name 1,4,7,10,13,16,19th youngest file
written in Display order meansfiles [0], files [3], files It can be said that it outputs [6], files [9], files [12], files [15], files [18]
.
If you find the number of files that can be output on one line and the number of lines and write the process to output files
well, the output will be as follows.
$ ruby ls.rb test
directory1directory3directory6directory9file2file5file8
directory10directory4directory7file1file3file6file9
directory2directory5directory8file10file4file7
It's not very readable, but it's one step closer to ls
.
At this point, you don't know where the file name is, so you need to separate the files.
I would like to confirm how the file name and the file name of the output result of ls
are separated.
Create a file with a name like print.rb
, and execute ls test
.
directory1 directory3
Copy and paste.
I would like to output this as a character string to the terminal.
Enclose it in " "
, prefix it with p
,
print.rb
p "directory1 directory13"
Save as.
When I run ruby print.rb
on the terminal, the output is as follows.
$ ruby print.rb
"directory1\tdirectory13"
The character \ t
appeared between directory1 and directory3.
This represents a tab character.
From this, you can see that the filenames are separated by a tab character.
I'll mimic this and add \ t
to the end of the filename.
Use the printf
method for that.
I will not explain printf
in detail here, so if you are interested, please read Documentation.
printf usage example
printf("%s\t", files[0]) # => %files in s[0]The contents are entered and output.
printf("%s\t", files[1])
# => "directory1 ""directory10 "
If implemented in ls.rb
, the output will be as follows.
$ ruby ls.rb test
directory1 directory3 directory6 directory9 file2 file5 file8
directory10 directory4 directory7 file1 file3 file6 file9
directory2 directory5 directory8 file10 file4 file7
It looks better than before, but it's still different from the ls
output.
Finally, unify the file name lengths to make them look nice.
Consider using directory10
and file1
, file2
, file3
.
The output at the moment looks like this.
directory10\tfile2
file1\tfile3
Due to the inconsistent length of file names, the number of characters in the output for each line is different.
If all filenames have the same length
file name\tfile name\t
file name\tfile name\t
Since it can be made to look beautiful like this, use whitespace characters and unify the length of the file name with the longest number of characters.
In this example, directory1
has the longest file name, so we will use the same length as directory1
.
#You can adjust the appearance by using white space!
directory\tfile2 \t
file1 \tfile3 \t
You can also unify the length of the string with the printf
method.
You can specify the width by slightly modifying % s
.
printf width specification
printf("%15s", files[0])
# => " directory1"Whitespace characters are added to 15 characters.
printf("%-15s", files[0])
# => "directory1 " -If you add, it will be left-justified.
name_len = 15
printf("%-#{name_len}s\t", files[0]) # =>Expression expansion is also possible.
# => "directory1 "
If you specify the width even in ls.rb
$ ruby ls.rb test
directory1 directory3 directory6 directory9 file2 file5 file8
directory10 directory4 directory7 file1 file3 file6 file9
directory2 directory5 directory8 file10 file4 file7
I was able to get the same output as ls
!
If you explain everything, it will be long, so I will explain only the part explained in Output format. In addition, we do not explain the method being called in detail, but only explain the function in the comment text.
The source code can be found in the Completed Code (#完成コード). If you are interested in the whole implementation, please see here.
self.display_normal
def self.display_normal(dir)
files = get_files(dir) #Assign an array of filenames in the directory to files.
name_len = 1 #Name that stores the maximum value of the file name_Prepare len.
files.map { |file| name_len = file.length if name_len < file.length } #The longest file name, name_Substitute in len.
total_length = (name_len + 5) * files.count #Calculate the number of characters to be output.
columns = `tput cols`.to_i #Get the number of characters in one line of the terminal.
line_count = (total_length + (columns - 1)) / columns #Find the required number of output lines.
column_count = (files.count + (line_count / 2)) / line_count #Find the number of files that can be displayed on one line.
line_count = 1 if line_count == 0 #At least one line needs to be output, so line_If count is 0, substitute 1 for it.
(0...line_count).each do |line|
(0..column_count).each do |column|
idx = line_count * column + line
printf("%-#{name_len}s\t", files[idx]) if idx < files_count #If the subscript does not exceed the size in the array, output it to the terminal
end
print("\n") #When you reach this point, the output for one line is finished, so start a new line.
end
end
Use the time
command to measure the execution time.
$ time ls test
directory1 directory3 directory6 directory9 file2 file5 file8
directory10 directory4 directory7 file1 file3 file6 file9
directory2 directory5 directory8 file10 file4 file7
ls test 0.00s user 0.00s system 78% cpu 0.008 total
#Run time of ls test 0.00s
$ time ruby ls.rb test
directory1 directory3 directory6 directory9 file2 file5 file8
directory10 directory4 directory7 file1 file3 file6 file9
directory2 directory5 directory8 file10 file4 file7
ruby ls.rb test 0.10s user 0.07s system 86% cpu 0.191 total
# ruby ls.rb execution time 0.10s
It's slower than ls
as you can see.
It's not very practical, but I think I was able to reproduce the ls command quite a bit. It's a poor code to call it redevelopment of the wheel, but I think it's more powerful than it was before.
I wanted to write a test or extend the function if I could afford it. Also, since I was always working with the document in one hand, I think I have acquired the ability to read and catch the document.
I couldn't explain it in this article, but I learned a lot about implementing ls -l
.
Since this Advent calendar has many articles related to the Web, I wanted to make an article that was a little different. It may be less of a direct learning experience than others, but I hope you find it helpful. Tomorrow is the final day. We hope that the article @ hide9138 will bring this Advent calendar to a good end. Have a nice Christmas: thumbs up:
Github : https://github.com/shinooooo/Ruby-sh
Ruby 2.7.0 Reference Manual Man page of LS
Recommended Posts