Class: Bashcov::Detective

Inherits:
Object
  • Object
show all
Defined in:
lib/bashcov/detective.rb

Overview

Detect shell scripts

Constant Summary collapse

SHELL_BASENAMES =
Set<String>

Basenames of shell executables

Set.new(%w[bash sh ash dash]).freeze
OTHER_BASENAMES =
Set<String>

Basenames of executables commonly used to exec other

processes, including shells
Set.new(%w[env]).freeze
SHELLSCRIPT_EXTENSIONS =
Set<String>

Filename extensions commonly used for shell scripts

Set.new(%w[.bash .sh]).freeze

Instance Method Summary collapse

Constructor Details

#initialize(bash_path) ⇒ Detective

Create an object that can be used for inferring whether a file is or is not a shell script.

Parameters:

  • bash_path (String)

    path to a Bash interpreter



21
22
23
# File 'lib/bashcov/detective.rb', line 21

def initialize(bash_path)
  @bash_path = bash_path
end

Instance Method Details

#shellscript?(filename) ⇒ Boolean

Note:

returns false when filename is not readable, even if filename indeed refers to a shell script.

Checks whether the provided file refers to a shell script by determining whether the first line is a shebang that refers to a shell executable, or whether the file has a shellscript extension and contains valid shell syntax.

Parameters:

  • filename (String, Pathname)

    the name of the file to be checked

Returns:

  • (Boolean)

    whether filename refers to a shell script



33
34
35
36
37
38
39
# File 'lib/bashcov/detective.rb', line 33

def shellscript?(filename)
  return false unless File.exist?(filename) && File.readable?(filename) \
    && File.file?(File.realpath(filename))

  shellscript_shebang?(filename) ||
    (shellscript_extension?(filename) && shellscript_syntax?(filename))
end

#shellscript_extension?(filename) ⇒ Boolean

extension

Parameters:

  • filename (String, Pathname)

    the name of the file to be checked

Returns:

  • (Boolean)

    whether filename‘s extension is a valid shellscript



85
86
87
# File 'lib/bashcov/detective.rb', line 85

def shellscript_extension?(filename)
  SHELLSCRIPT_EXTENSIONS.include? File.extname(filename)
end

#shellscript_shebang?(filename) ⇒ Boolean

Note:

assumes that filename is readable and refers to a regular file

Returns whether filename‘s first line is a valid shell shebang.

Parameters:

  • filename (String, Pathname)

    the name of the file to be checked

Returns:

  • (Boolean)

    whether filename‘s first line is a valid shell shebang



45
46
47
48
49
50
51
52
53
54
# File 'lib/bashcov/detective.rb', line 45

def shellscript_shebang?(filename)
  # Handle empty files that cause an immediate EOFError
  begin
    shebang = File.open(filename) { |f| f.readline.chomp }
  rescue EOFError
    return false
  end

  shellscript_shebang_line?(shebang)
end

#shellscript_shebang_line?(shebang) ⇒ Boolean

Returns whether the line is a valid shell shebang.

Parameters:

  • shebang (String)

    a line to test for shell shebang-itude

Returns:

  • (Boolean)

    whether the line is a valid shell shebang



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/bashcov/detective.rb', line 58

def shellscript_shebang_line?(shebang)
  scanner = StringScanner.new(shebang)

  begin
    return false if scanner.scan(/#!\s*/).nil?

    shell = scanner.scan(/\S+/)

    return false if shell.nil?

    args = scanner.skip(/\s+/).nil? ? [] : scanner.rest.split(/\s+/)
  rescue ArgumentError
    # Handle "invalid byte sequence in UTF-8" from `StringScanner`.  Can
    # happen when trying to read binary data (e.g. .pngs).
    return false
  end

  shell_basename = File.basename(shell)

  SHELL_BASENAMES.include?(shell_basename) ||
    (OTHER_BASENAMES.include?(shell_basename) &&
      args.any? { |arg| SHELL_BASENAMES.include?(File.basename(arg)) })
end