Task 660: .SH File Format

Task 660: .SH File Format

1. File Format Specifications for .SH

The .sh file extension is primarily associated with Unix shell scripts, which are plain text files containing executable commands written in shell scripting languages such as Bourne Shell (sh), Bash, or compatible variants. These files are not a binary format with a rigid structure but rather human-readable text files designed for interpretation by a shell environment. There is no formal "specification" akin to binary formats (e.g., PNG or ZIP); instead, adherence to POSIX standards for shell command syntax governs their behavior. Key conventions include:

  • Shebang Line: An optional initial line starting with #! followed by the path to the interpreter (e.g., #!/bin/sh or #!/bin/bash), which specifies the shell to execute the script.
  • Content Structure: A sequence of commands, variables, conditionals, loops, and comments (prefixed with # for lines or inline after commands).
  • Encoding: Typically UTF-8 or ASCII, with Unix-style line endings (LF, \n).
  • Execution Model: Files are executed sequentially by the shell, supporting features like redirection (>), piping (|), and environment variable substitution ($VAR).

A secondary, less common use of .sh is for shell archives (shar files), which embed files and extraction instructions in a self-extracting script. However, this is distinct from standard shell scripts and uses the same extension. The primary interpretation here is shell scripts, as it aligns with the context of developing code for automation and file system interactions.

2. List of Properties Intrinsic to Its File System

As plain text files integrated into Unix-like file systems (e.g., ext4, APFS), .sh files inherit standard file system attributes. Intrinsic properties refer to those fundamental to the file's storage, access, and execution within the file system, excluding content semantics. The following list enumerates these properties:

  • File Name and Extension: The base name (e.g., script.sh) with the .sh extension, which serves as a convention for identification but is not enforced by the file system.
  • File Size: The byte length of the file content, measured in bytes (e.g., for storage allocation).
  • Permissions (Mode): Octal or symbolic access rights, including read (r), write (w), execute (x) for owner/group/others (e.g., 755 for executable scripts). The execute bit is crucial for direct invocation.
  • Owner (UID): User ID of the file owner, determining primary access control.
  • Group (GID): Group ID associated with the file, for shared access permissions.
  • Timestamps:
  • Modification Time (mtime): Last content change timestamp.
  • Access Time (atime): Last read access timestamp.
  • Change Time (ctime): Last metadata (e.g., permissions) change timestamp.
  • Inode Number: Unique file system identifier for the file's data block location.
  • Hard Link Count: Number of directory entries pointing to the same inode (typically 1 for unique files).
  • Type: Regular file (not directory or symlink), indicated by file system flags.
  • Extended Attributes: Optional metadata (e.g., SELinux contexts or ACLs) if supported by the file system.

These properties are accessible via system calls (e.g., stat() in C) or commands like ls -l and stat.

Two direct download links for sample .sh files are provided below. These are simple, safe shell scripts from reputable sources:

  1. https://filesamples.com/samples/code/sh/-sample-1.sh (A basic "Hello World" shell script example.)
  2. https://raw.githubusercontent.com/ruanyf/simple-bash-scripts/master/DirectorySize.sh (A script to compute directory sizes from GitHub.)

4. Ghost Blog Embedded HTML JavaScript

The following is a self-contained HTML snippet with embedded JavaScript, suitable for embedding in a Ghost blog post (e.g., via the HTML card). It enables drag-and-drop upload of a .sh file, validates the extension, reads the file as text, and extracts/displays the intrinsic file system properties listed in section 2 (where accessible via the File API) plus content-specific details like shebang and line count. Properties like inode or UID are not directly accessible in browser JavaScript and are noted as such.

Drag and drop a .sh file here to analyze its properties.

To embed in Ghost: Paste into an HTML card in the editor. Users can drag/drop or click to select (via hidden input).

5. Python Class for .SH Handling

The following Python class opens a .sh file, reads its content, extracts the listed properties (using os and stat for file system intrinsics), prints them to console, and supports writing (e.g., updating content while preserving properties where possible). It assumes a Unix-like environment for full property access.

import os
import stat
from datetime import datetime

class SHFile:
    def __init__(self, path):
        self.path = path
        self.stat = os.stat(path)
        with open(path, 'r') as f:
            self.content = f.read()
        self.lines = self.content.splitlines()
        self.shebang = self.lines[0] if self.lines and self.lines[0].startswith('#!') else 'None'

    def print_properties(self):
        props = {
            'File Name': os.path.basename(self.path),
            'File Size (bytes)': self.stat.st_size,
            'Permissions (octal)': oct(self.stat.st_mode),
            'Owner UID': self.stat.st_uid,
            'Group GID': self.stat.st_gid,
            'Modification Time (mtime)': datetime.fromtimestamp(self.stat.st_mtime).isoformat(),
            'Access Time (atime)': datetime.fromtimestamp(self.stat.st_atime).isoformat(),
            'Change Time (ctime)': datetime.fromtimestamp(self.stat.st_ctime).isoformat(),
            'Inode Number': self.stat.st_ino,
            'Hard Link Count': self.stat.st_nlink,
            'Type': 'Regular file' if stat.S_ISREG(self.stat.st_mode) else 'Other',
            'Shebang Line': self.shebang,
            'Line Count': len(self.lines)
        }
        for k, v in props.items():
            print(f"{k}: {v}")

    def write(self, new_content):
        with open(self.path, 'w') as f:
            f.write(new_content)
        self.__init__(self.path)  # Refresh properties after write

# Example usage:
# shf = SHFile('example.sh')
# shf.print_properties()
# shf.write('#!/bin/bash\necho "Updated"')
# shf.print_properties()

6. Java Class for .SH Handling

The following Java class uses java.nio.file to open a .sh file, extract properties via Files and Attributes, print to console, and support writing. It requires Java 8+. Extended attributes are omitted for brevity, as they vary by file system.

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.util.List;

public class SHFile {
    private Path path;
    private String content;
    private String shebang;

    public SHFile(String pathStr) throws IOException {
        this.path = Paths.get(pathStr);
        this.content = Files.readString(this.path, StandardCharsets.UTF_8);
        List<String> lines = content.lines().toList();
        this.shebang = (lines.size() > 0 && lines.get(0).startsWith("#!")) ? lines.get(0) : "None";
    }

    public void printProperties() throws IOException {
        BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
        PosixFileAttributes posixAttrs = Files.readAttributes(path, PosixFileAttributes.class);
        FileTime mtime = attrs.lastModifiedTime();
        FileTime atime = attrs.lastAccessTime();
        FileTime ctime = attrs.lastModifiedTime();  // Approximation for ctime
        System.out.println("File Name: " + path.getFileName());
        System.out.println("File Size (bytes): " + attrs.size());
        System.out.println("Permissions (octal): " + Integer.toOctalString(posixAttrs.permissions().hashCode()));  // Simplified
        System.out.println("Owner UID: " + posixAttrs.owner().getName(0, -1));  // User name approximation
        System.out.println("Group GID: " + posixAttrs.group().getName(0, -1));
        System.out.println("Modification Time (mtime): " + mtime.toInstant().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        System.out.println("Access Time (atime): " + atime.toInstant().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        System.out.println("Change Time (ctime): " + ctime.toInstant().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        System.out.println("Inode Number: " + attrs.fileKey().hashCode());  // Approximation
        System.out.println("Hard Link Count: " + attrs.isDirectory() ? "N/A" : 1);  // Basic
        System.out.println("Type: " + (attrs.isRegularFile() ? "Regular file" : "Other"));
        System.out.println("Shebang Line: " + shebang);
        System.out.println("Line Count: " + content.lines().count());
    }

    public void write(String newContent) throws IOException {
        Files.writeString(path, newContent, StandardCharsets.UTF_8);
        this.content = newContent;
        List<String> lines = content.lines().toList();
        this.shebang = (lines.size() > 0 && lines.get(0).startsWith("#!")) ? lines.get(0) : "None";
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     SHFile shf = new SHFile("example.sh");
    //     shf.printProperties();
    //     shf.write("#!/bin/bash\necho \"Updated\"");
    //     shf.printProperties();
    // }
}

7. JavaScript Class for .SH Handling

The following Node.js-compatible JavaScript class uses fs (File System module) to read/write .sh files, extract properties, and print to console. Run with Node.js (e.g., node shfile.js). Browser use would require File API adaptations, as in section 4.

const fs = require('fs').promises;
const path = require('path');
const { stat } = require('fs');

class SHFile {
  constructor(filePath) {
    this.path = filePath;
    this.content = null;
    this.shebang = 'None';
  }

  async init() {
    this.content = await fs.readFile(this.path, 'utf8');
    const lines = this.content.split('\n');
    if (lines.length > 0 && lines[0].startsWith('#!')) {
      this.shebang = lines[0];
    }
  }

  printProperties() {
    stat(this.path, (err, stats) => {
      if (err) throw err;
      console.log('File Name:', path.basename(this.path));
      console.log('File Size (bytes):', stats.size);
      console.log('Permissions (octal):', stats.mode.toString(8));
      console.log('Owner UID:', stats.uid);
      console.log('Group GID:', stats.gid);
      console.log('Modification Time (mtime):', new Date(stats.mtime).toISOString());
      console.log('Access Time (atime):', new Date(stats.atime).toISOString());
      console.log('Change Time (ctime):', new Date(stats.ctime).toISOString());
      console.log('Inode Number:', stats.ino);
      console.log('Hard Link Count:', stats.nlink);
      console.log('Type:', stats.isFile() ? 'Regular file' : 'Other');
      console.log('Shebang Line:', this.shebang);
      console.log('Line Count:', this.content.split('\n').length);
    });
  }

  async write(newContent) {
    await fs.writeFile(this.path, newContent, 'utf8');
    this.content = newContent;
    const lines = newContent.split('\n');
    this.shebang = (lines.length > 0 && lines[0].startsWith('#!')) ? lines[0] : 'None';
  }
}

// Example usage:
// (async () => {
//   const shf = new SHFile('example.sh');
//   await shf.init();
//   shf.printProperties();
//   await shf.write('#!/bin/bash\necho "Updated"');
//   shf.printProperties();
// })();

8. C Class (Struct with Functions) for .SH Handling

The following C implementation uses POSIX functions (stat, fopen) for a Unix-like environment. It defines a struct as a "class" equivalent, with functions to read properties, print to stdout, and write content. Compile with gcc -o shfile shfile.c and run ./shfile example.sh.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <utime.h>

typedef struct {
    char *path;
    char *content;
    char *shebang;
    struct stat st;
    int lines;
} SHFile;

SHFile *shfile_new(const char *path) {
    SHFile *self = malloc(sizeof(SHFile));
    self->path = strdup(path);
    self->content = NULL;
    self->shebang = strdup("None");
    self->lines = 0;

    if (stat(path, &self->st) != 0) {
        perror("stat");
        free(self);
        return NULL;
    }

    FILE *f = fopen(path, "r");
    if (!f) {
        perror("fopen");
        free(self->path);
        free(self->shebang);
        free(self);
        return NULL;
    }

    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    rewind(f);
    self->content = malloc(size + 1);
    fread(self->content, 1, size, f);
    self->content[size] = '\0';
    fclose(f);

    // Count lines and check shebang
    self->lines = 1;
    for (int i = 0; i < size; i++) {
        if (self->content[i] == '\n') self->lines++;
    }
    if (size > 2 && strncmp(self->content, "#!", 2) == 0) {
        free(self->shebang);
        char *nl = strchr(self->content, '\n');
        int len = nl ? (nl - self->content) : size;
        self->shebang = malloc(len + 1);
        strncpy(self->shebang, self->content, len);
        self->shebang[len] = '\0';
    }

    return self;
}

void shfile_print_properties(SHFile *self) {
    char timebuf[64];
    struct tm *tm = localtime(&self->st.st_mtime);
    strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", tm);
    printf("File Name: %s\n", self->path ? strrchr(self->path, '/') + 1 : "N/A");
    printf("File Size (bytes): %ld\n", (long)self->st.st_size);
    printf("Permissions (octal): %o\n", self->st.st_mode & 0777);
    printf("Owner UID: %d\n", self->st.st_uid);
    printf("Group GID: %d\n", self->st.st_gid);
    printf("Modification Time (mtime): %s\n", timebuf);
    tm = localtime(&self->st.st_atime);
    strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", tm);
    printf("Access Time (atime): %s\n", timebuf);
    tm = localtime(&self->st.st_ctime);
    strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", tm);
    printf("Change Time (ctime): %s\n", timebuf);
    printf("Inode Number: %ld\n", (long)self->st.st_ino);
    printf("Hard Link Count: %ld\n", (long)self->st.st_nlink);
    printf("Type: %s\n", S_ISREG(self->st.st_mode) ? "Regular file" : "Other");
    printf("Shebang Line: %s\n", self->shebang);
    printf("Line Count: %d\n", self->lines);
}

void shfile_write(SHFile *self, const char *new_content) {
    FILE *f = fopen(self->path, "w");
    if (!f) {
        perror("fwrite");
        return;
    }
    fputs(new_content, f);
    fclose(f);

    // Refresh
    free(self->content);
    self->content = strdup(new_content);
    self->lines = 1;
    for (size_t i = 0; new_content[i]; i++) {
        if (new_content[i] == '\n') self->lines++;
    }
    free(self->shebang);
    self->shebang = strdup("None");
    if (strlen(new_content) > 2 && strncmp(new_content, "#!", 2) == 0) {
        const char *nl = strchr(new_content, '\n');
        size_t len = nl ? (nl - new_content) : strlen(new_content);
        self->shebang = malloc(len + 1);
        strncpy(self->shebang, new_content, len);
        self->shebang[len] = '\0';
    }
    stat(self->path, &self->st);  // Refresh stat
}

void shfile_free(SHFile *self) {
    free(self->path);
    free(self->content);
    free(self->shebang);
    free(self);
}

// Example usage:
// int main() {
//     SHFile *shf = shfile_new("example.sh");
//     if (shf) {
//         shfile_print_properties(shf);
//         shfile_write(shf, "#!/bin/sh\necho \"Updated\"");
//         shfile_print_properties(shf);
//         shfile_free(shf);
//     }
//     return 0;
// }