Ag - The Silver Searcher
Tìm kiếm đoạn code nào đó là điều mà hầu hết chúng ta thực hiện hàng ngày. Cho dù đó là để refactor hoặc chỉ đơn giản là tìm nơi một biến, lớp, hoặc phương thức đã được thực hiện. Các công cụ tìm kiếm không tốt có thể khiến công việc của chúng ta khó khăn hơn mức cần thiết. Trong bài viết này, ...
Tìm kiếm đoạn code nào đó là điều mà hầu hết chúng ta thực hiện hàng ngày. Cho dù đó là để refactor hoặc chỉ đơn giản là tìm nơi một biến, lớp, hoặc phương thức đã được thực hiện. Các công cụ tìm kiếm không tốt có thể khiến công việc của chúng ta khó khăn hơn mức cần thiết.
Trong bài viết này, tôi sẽ giới thiệu một công cụ có thể giúp tìm kiếm một số lượng lớn các tệp nhanh chóng và dễ dàng. Đó là ag.
Chúng ta sẽ muốn có một khối lượng code khá lớn để tìm kiếm nhằm thể hiện sức mạnh của ag, vì vậy trong bài viết này chúng ta sẽ sử dụng source code Rails để thực hiện demo.
Installation
Nếu bạn đang sử dụng máy Mac, cách tốt nhất và dễ nhất để cài đặt ag là sử dụng Homebrew.
$ brew install ag
Đối với Ubuntu >= 13.10 (Saucy) hoặc Debian >= 8 (Jessie)
apt-get install silversearcher-ag
Ngoài ra để cài đặt cho các hệ điều hành khác, mọi người có thể đọc ở đây
The Basics
Cú pháp của một lệnh Ag là
ag [FILE-TYPE] [OPTIONS] PATTERN [PATH]
Hãy bắt đầu bằng cách xem xét loại tìm kiếm cơ bản nhất mà chúng ta có thể thực hiện bằng ag.
Tác giả của framework Ruby on Rails là một người có tên viết tắt là DHH. Hãy xem liệu ta có thể tìm thấy bất kỳ sự xuất hiện của những chữ cái đó trong toàn bộ source code của Rails hay không
$ ag DHH
Kết quả tìm kiếm sẽ như thế này
activerecord/CHANGELOG.md 47: *DHH* 51: *DHH* activerecord/lib/active_record/relation.rb 51: # users = User.where(name: 'DHH') 52: # user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil> activerecord/lib/active_record/relation/query_methods.rb 765: # users = users.create_with(name: 'DHH') 766: # users.new.name # => 'DHH' activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb 21: # Thread.current[:attr_Current_user] = "DHH" 22: # Current.user # => "DHH" 71: # Current.user = "DHH" 72: # Thread.current[:attr_Current_user] # => "DHH" 81: # Current.new.user = "DHH" # => NoMethodError 112: # Account.user = "DHH" 113: # Account.user # => "DHH" 114: # Account.new.user # => "DHH" 125: # Account.user # => "DHH" 134: # Current.new.user = "DHH" # => NoMethodError 143: # Current.new.user = "DHH" # => NoMethodError actionview/lib/action_view/helpers/atom_feed_helper.rb 45: # author.name("DHH") 78: # author.name("DHH") actionview/test/template/atom_feed_helper_test.rb 27: author.name("DHH") 44: author.name("DHH") 61: author.name("DHH") 78: author.name("DHH") 90: author.name("DHH") 114: author.name("DHH") 132: author.name("DHH") 150: author.name("DHH") 168: author.name("DHH") 190: author.name("DHH") 210: author.name("DHH") 298: assert_select "author name", text: "DHH" actioncable/lib/action_cable/channel/streams.rb 28: # ActionCable.server.broadcast "comments_for_45", author: 'DHH', content: 'Rails is just swell' activestorage/test/models/attachments_test.rb 9: setup { @user = User.create!(name: "DHH") } activestorage/test/template/image_tag_test.rb 34: @user = User.create!(name: "DHH") guides/source/caching_with_rails.md 66:INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. 686:* [DHH's article on key-based expiration](https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) guides/source/active_record_migrations.md 112:`YYYYMMDDHHMMSS_create_products.rb`, that is to say a UTC timestamp 230:`db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` file. guides/source/getting_started.md 687:If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file
Như bạn có thể thấy, đối số đầu tiên chúng ta chuyển vào là cụm từ tìm kiếm mà chúng ta muốn tìm trong thư mục tệp của chúng ta. Theo mặc định, truy vấn này được chạy đệ quy đối với thư mục hiện tại.
Nếu chúng ta muốn chỉ định một đường dẫn để tìm kiếm, chúng ta có thể truyền vào đối số cuối cùng. Ví dụ: nếu ta chỉ muốn tìm kiếm trong thư mục guides, ta có thể sửa đổi truy vấn của mình để trông giống như sau:
$ ag DHH guides/
Kết quả của ta bây giờ sẽ chỉ giới hạn trong thư mục đó như dưới đây
guides/source/caching_with_rails.md 66:INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. 686:* [DHH's article on key-based expiration](https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) guides/source/active_record_migrations.md 112:`YYYYMMDDHHMMSS_create_products.rb`, that is to say a UTC timestamp 230:`db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` file. guides/source/getting_started.md 687:If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file
Regular Expression Searches
Ag có hỗ trợ tìm kiếm theo regular expression. Điều này có thể cực kỳ hữu ích cho việc tạo các truy vấn tìm kiếm chi tiết hơn.
Bây giờ ta hãy thử tìm kiếm bất kỳ sự xuất hiện nào của từ readme ở cuối dòng.
$ ag readme$
Kết quả là
railties/lib/rails/generators/rails/app/app_generator.rb 50: def readme railties/lib/rails/generators/rails/app/templates/README.md.tt 1:# README railties/lib/rails/generators/rails/plugin/plugin_generator.rb 40: def readme railties/test/generators/actions_test.rb 427: def test_readme
Literal Expression Searches
Mặc định Ag xem cụm từ tìm kiếm là một regular expression. Điều này có nghĩa là nếu chúng ta tìm kiếm mẫu .rb nó sẽ khớp với bất kỳ ký tự đơn nào theo sau là rb. Đây có thể không phải là những gì chúng ta muốn. Hãy tìm kiếm .rb trong railties/CHANGELOG.md.
$ ag .rb railties/CHANGELOG.md
5: Skip `config/spring.rb` when spring isn't loaded. 61: Verb | GET 66: Verb | GET
Ở trên liệt kê các kết quả tìm kiếm của chúng ta. Vì Ag tìm .rb như một regular expression, kết quả của ta bao gồm một số thứ không mong muốn, chẳng hạn như Verb.
Để khắc phục điều này, chúng ta có thể thêm vào truy vấn của mình option -Q. Điều này sẽ tìm kiếm chính xác .rb trong file.
$ ag -Q .rb railties/CHANGELOG.md
5: Skip `config/spring.rb` when spring isn't loaded.
Listing Files (-l)
Theo mặc định, khi thực hiện tìm kiếm với Ag, kết quả sẽ bao gồm số dòng cũng như dòng mà chứa từ được tìm thấy.
Mức độ chi tiết này vô cùng hữu ích trong nhiều trường hợp, nhưng nếu chúng ta thực sự chỉ muốn xem danh sách các tên file, mà không thấy bối cảnh của từ được tìm thấy thì option -l có thể giúp chúng ta với điều này.
$ ag DHH -l
Kết quả hiện được hiển thị dưới dạng danh sách các file
activestorage/test/template/image_tag_test.rb activestorage/test/models/attachments_test.rb activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb actioncable/lib/action_cable/channel/streams.rb guides/source/caching_with_rails.md guides/source/active_record_migrations.md guides/source/getting_started.md activerecord/lib/active_record/relation.rb activerecord/lib/active_record/relation/query_methods.rb activerecord/CHANGELOG.md actionview/test/template/atom_feed_helper_test.rb actionview/lib/action_view/helpers/atom_feed_helper.rb
Danh sách các file được hiển thị như thế này giúp chúng ta dễ quản lý hơn một chút và có thể cho chúng ta biết rõ hơn về số lượng file trong kết quả của truy vấn.
Scoping to Files (-G)
Rất thường xuyên, chúng ta sẽ chỉ muốn tìm kiếm các loại file nhất định hoặc trong các file có phần mở rộng cụ thể. Để thực hiện điều này, chúng tôi có thể sử dụng option -G
Bây giờ ta sẽ thử tìm kiếm readme trong source code, nhưng lần này ta sẽ giới hạn kết quả tìm kiếm vào những file có tên chứa từ action.
$ ag readme -l -G action
railties/test/generators/actions_test.rb railties/lib/rails/generators/actions.rb
Hãy sửa đổi truy vấn của chúng ta một chút và tìm kiếm các file có tên kết thúc bằng ec.
$ ag readme -l -i -G ec
Như kết quả bên dưới, chúng ta không hoàn toàn có kết quả truy vấn như mong muốn, vì nó cũng trả về các file như activerecord/lib/active_record/base.rb, có chứa ec trong từ record
rails.gemspec activestorage/activestorage.gemspec railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt railties/railties.gemspec actionpack/actionpack.gemspec activejob/activejob.gemspec activesupport/activesupport.gemspec activemodel/activemodel.gemspec actioncable/actioncable.gemspec activerecord/lib/active_record/base.rb activerecord/activerecord.gemspec actionview/actionview.gemspec actionmailer/actionmailer.gemspec
Thay vì tìm các file kết thúc bằng ec, kết quả tìm kiếm hiển thị cho ta các file có chứa ec ở bất kỳ đâu trong tên file. Làm cách nào để chúng ta khắc phục truy vấn của mình để lấy lại kết quả mà chúng ta muốn?
-G cho phép chúng ta truyền các regular expressions, vì vậy ta có thể dễ dàng sửa đổi truy vấn tìm kiếm của mình để cải thiện kết quả bằng cách sử dụng $ để cho biết rằng ta chỉ muốn tìm với tên file kết thúc bằng ec.
$ ag readme -l -i -G ec$
activestorage/activestorage.gemspec rails.gemspec railties/railties.gemspec actionpack/actionpack.gemspec activejob/activejob.gemspec activesupport/activesupport.gemspec activemodel/activemodel.gemspec actioncable/actioncable.gemspec activerecord/activerecord.gemspec actionview/actionview.gemspec actionmailer/actionmailer.gemspec
Như bạn có thể thấy, kết quả tìm kiếm của ta hiện chỉ chứa các file có tên kết thúc bằng ec.
Ignoring Paths
Một cách quan trọng khác để lọc kết quả tìm kiếm là bỏ qua một số thư mục nhất định mà chúng ta không quan tâm. Ag có một vài tính năng hữu ích giúp chúng ta làm điều đó.
–ignore-dir option
Hãy bắt đầu với truy vấn tìm từ readme và kết quả của nó
$ ag readme -l
activestorage/test/dummy/app/assets/javascripts/application.js activestorage/activestorage.gemspec rails.gemspec railties/test/generators/api_app_generator_test.rb railties/test/generators/plugin_generator_test.rb railties/test/generators/actions_test.rb railties/test/generators/app_generator_test.rb railties/RDOC_MAIN.rdoc railties/lib/rails/generators/app_base.rb railties/lib/rails/generators/rails/plugin/USAGE railties/lib/rails/generators/rails/plugin/plugin_generator.rb railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js.tt railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt railties/lib/rails/generators/rails/app/app_generator.rb railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt railties/lib/rails/generators/rails/app/templates/README.md.tt railties/lib/rails/generators/actions.rb railties/lib/rails/api/task.rb railties/railties.gemspec actionpack/test/controller/action_pack_assertions_test.rb actionpack/actionpack.gemspec activejob/lib/active_job/queue_adapters.rb activejob/activejob.gemspec activesupport/activesupport.gemspec activemodel/activemodel.gemspec actioncable/actioncable.gemspec guides/source/rails_application_templates.md guides/source/configuring.md guides/source/5_0_release_notes.md guides/source/plugins.md guides/source/upgrading_ruby_on_rails.md guides/source/working_with_javascript_in_rails.md guides/source/command_line.md guides/source/asset_pipeline.md guides/source/contributing_to_ruby_on_rails.md guides/source/4_1_release_notes.md guides/source/getting_started.md guides/source/generators.md activerecord/lib/active_record/base.rb activerecord/activerecord.gemspec actionview/actionview.gemspec actionview/package.json README.md actionmailer/actionmailer.gemspec
Điều gì sẽ xảy ra nếu chúng ta không muốn nhìn thấy bất kỳ kết quả nào từ thư mục railties/lib? Để giải quyết vấn đề đó, chúng ta có thể sử dụng option -ignore-dir.
$ ag readme -l --ignore-dir=railties/lib
Tất cả các kết quả từ các file nằm trong thư mục đó sẽ bị bỏ qua.
activestorage/test/dummy/app/assets/javascripts/application.js activestorage/activestorage.gemspec rails.gemspec railties/test/generators/api_app_generator_test.rb railties/test/generators/plugin_generator_test.rb railties/test/generators/actions_test.rb railties/test/generators/app_generator_test.rb railties/RDOC_MAIN.rdoc railties/railties.gemspec actionpack/test/controller/action_pack_assertions_test.rb actionpack/actionpack.gemspec activejob/lib/active_job/queue_adapters.rb activejob/activejob.gemspec activesupport/activesupport.gemspec activemodel/activemodel.gemspec actioncable/actioncable.gemspec guides/source/rails_application_templates.md guides/source/configuring.md guides/source/5_0_release_notes.md guides/source/plugins.md guides/source/upgrading_ruby_on_rails.md guides/source/working_with_javascript_in_rails.md guides/source/command_line.md guides/source/asset_pipeline.md guides/source/contributing_to_ruby_on_rails.md guides/source/4_1_release_notes.md guides/source/getting_started.md guides/source/generators.md activerecord/lib/active_record/base.rb activerecord/activerecord.gemspec actionview/actionview.gemspec actionview/package.json README.md actionmailer/actionmailer.gemspec
Chúng ta có thể dễ dàng nối nhiều -ignore-dir với nhau để lọc kết quả của chúng ta hơn nữa.
$ ag readme -l --ignore-dir=railties/lib --ignore-dir=guides/source
activestorage/test/dummy/app/assets/javascripts/application.js activestorage/activestorage.gemspec rails.gemspec railties/test/generators/api_app_generator_test.rb railties/test/generators/plugin_generator_test.rb railties/test/generators/actions_test.rb railties/test/generators/app_generator_test.rb railties/RDOC_MAIN.rdoc railties/railties.gemspec actionpack/test/controller/action_pack_assertions_test.rb actionpack/actionpack.gemspec activejob/lib/active_job/queue_adapters.rb activejob/activejob.gemspec activemodel/activemodel.gemspec activesupport/activesupport.gemspec actioncable/actioncable.gemspec activerecord/lib/active_record/base.rb activerecord/activerecord.gemspec actionview/actionview.gemspec actionview/package.json README.md actionmailer/actionmailer.gemspec
-ignore-dir cũng sẽ cho phép chúng ta lọc ra các file cụ thể, không chỉ là các thư mục.
$ ag readme -l --ignore-dir="*.rb" --ignore-dir="*.gemspec"
activestorage/test/dummy/app/assets/javascripts/application.js railties/RDOC_MAIN.rdoc railties/lib/rails/generators/rails/plugin/USAGE railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js.tt railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt railties/lib/rails/generators/rails/app/templates/README.md.tt guides/source/rails_application_templates.md guides/source/configuring.md guides/source/5_0_release_notes.md guides/source/plugins.md guides/source/upgrading_ruby_on_rails.md guides/source/working_with_javascript_in_rails.md guides/source/command_line.md guides/source/asset_pipeline.md guides/source/contributing_to_ruby_on_rails.md guides/source/4_1_release_notes.md guides/source/getting_started.md guides/source/generators.md actionview/package.json README.md
VCS Ignore Files
Ag sẽ tự động đọc trong các file VCS (version control system), chẳng hạn như .gitignore đối với Git và kết quả trả về sẽ bỏ qua những file có trong .gitignore
Ví dụ, source code của Rails đã có một file .gitignore, ta hãy thêm các thư mục railties/lib và guides/source vào đó
.Gemfile .byebug_history .ruby-version /*/doc/ /*/test/tmp/ /.bundle /dist/ /doc/ /guides/output/ debug.log node_modules/ package-lock.json pkg/ railties/lib guides/source
Bây giờ nếu chúng ta chạy cùng một truy vấn, nhưng không có option -ignore-dir, ag sẽ trả về kết quả giống với lệnh
$ ag readme -l --ignore-dir=railties/lib --ignore-dir=guides/source
Tất nhiên, có thể có những lúc nếu bạn không muốn sử dụng tính năng này, sử dụng option –skip-vcs-ignores chúng ta có thể nhận được kết quả ban đầu của truy vấn của mình. Option -skip-vcs-ignores sẽ bỏ qua các file sau: .gitignore, .hgignore, .svnignore.
Searching from Standard Input
Ag có thể sử kết quả của lệnh khác để cung cấp dữ liệu rồi tìm kiếm. Ví dụ, chúng ta có thể sử dụng lệnh ps để lấy danh sách tất cả các tiến trình đang chạy trên máy của chúng ta, và sau đó tìm kiếm cụ thể bằng cách sử dụng Ag.
ps -e | ag redis
8911 ? 00:00:00 redis-server
Ở trên ta tìm kiếm qua tất cả các tiến trình đang chạy để tìm một tiến trình có chứa từ redis
Khả năng này cực kỳ hữu ích, không chỉ cho việc tìm kiếm những thứ như các tiến trình, mà còn để đọc file đầu vào từ một lệnh như cat, và một số ứng dụng khác
Một cách khác để thực hiện tìm kiếm tương tự này là sử dụng lệnh grep