Tomorrow, the 1st of August is a national holiday in Switzerland … So I do one day off and have some time. For a long time I wanted to deal with Shell lint. After some research, i found a few open-source tools. By the way … linters are being written for many programming languages and document formats.
Preparation
For evaluation i will not install the tools on my local system,… so Vagrant (with CentOS 7) is my choice.
# create project $ mkdir -p ~/Projects/ShellLint && cd ~/Projects/ShellLint # create example.sh $ vim example.sh # create Vagrantfile $ vim Vagrantfile
#!/bin/bash declare -r VERSION="1.0.0" declare -r FILE_NAME=$(basename "$0") function fc_usage() { printf "Usage: %s" "$FILE_NAME" printf " [-h] [-V]\n" } function failure() { print "here is a error" syntax() { print "this line has simply to many chars ... with a simple shell lint you should see" } function fc_bashism() { echo -e "hello world" } function main() { fc_usage fc_bashism } exit 0
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.box = "lupin/centos7" config.vm.box_check_update = false config.vm.synced_folder '.', '/vagrant', disabled: true config.vm.provision "file", source: "example.sh", destination: "example.sh" config.vm.provider "virtualbox" do |vb| vb.gui = false vb.name = "ShellLint" vb.memory = "1024" vb.cpus = 1 end end
Note: I created the Vagrant box “lupin/centos” via Packer … here my GitHub repository.
# create environment $ vagrant up # SSH into VM $ vagrant ssh
Shell option -n
Many shell’s already offer a very simple script analysis. The option -n read commands in script, but do not execute them (syntax check).
# example bash $ bash -n example.sh # example shell $ sh -n example.sh
Okay … but not really what I want… (more details are welcome)
shlint and checkbashisms
I found the repository here.
# install ruby (optional) $ yum install -y ruby # install json_pure (optional) $ gem install json_pure # install shlint $ gem install shlint # create config $ echo -e 'shlint_shells="bash sh"\nshlint_debug=1' > .shlintrc # run shlint $ shlint example.sh # run checkbashisms $ checkbashisms -f example.sh
Note: for both tools you should change the shebang to “#!/bin/sh”
For shlint… I don’t get it. For checkbashisms … good if will write portable Shell scripts.
bashate
I found it here on Pypi.
# install epel repository $ yum install -y epel-release # install pip (python 2.x) $ yum install -y python2-pip # install bashate $ pip install bashate # run bashate $ bashate example.sh
Nice … but not really all Standards.
Shellsheck
Shellcheck is known! Here the online service and here the repository.
# install epel repository $ yum install -y epel-release # update (optional) $ yum update -y # install ShellCheck $ yum install -y ShellCheck # run ShellCheck $ shellcheck example.sh
I stay with that tool. Currently there are packages for almost every known OS.
Additional
Who knows me … knows that I do not like Installer and prefer Docker use. Here’s some fun.
# exit Vagrant and destroy $ vagrant halt && vagrant destroy -f # create Dockerfile $ vim Dockerfile # create Applescript $ vim linter.scpt # build Docker image $ docker build -t alpine/shellcheck . # change permission $ chmod +x linter.scpt # run Applescript $ osascript linter.scpt
FROM alpine:latest # install needed packages RUN apk --update add wget # download archive RUN wget -q --no-check-certificate https://storage.googleapis.com/shellcheck/shellcheck-latest.linux.x86_64.tar.xz # unzip archive RUN tar xvfJ shellcheck-latest.linux.x86_64.tar.xz # move binary RUN mv /shellcheck-latest/shellcheck /usr/local/bin/shellcheck # cleanup RUN apk del wget RUN rm -f shellcheck-latest.linux.x86_64.tar.xz RUN rm -fr shellcheck-latest/ # change to mount directory WORKDIR /mnt # set entrypoint ENTRYPOINT ["/usr/local/bin/shellcheck"]
#!/usr/bin/osascript -- define global variables global appName global imageName -- set magic values set appName to "Docker" set imageName to "alpine/shellcheck " -- run docker linters on LintShell(macPath) set posixPath to quoted form of POSIX path of macPath set fileName to do shell script "basename " & posixPath set dirName to do shell script "dirname " & posixPath set shellCmd to "docker run --rm -i -v " & dirName & ":/mnt " & imageName & fileName tell application "Terminal" set shell to do script shellCmd in front window end tell end LintShell -- display select box on SelectFile() set dlTitle to "Nothing selected..." set dlMsg to "Process is terminated." try set macPath to choose file on error display dialog dlMsg buttons ["OK"] with title dlTitle return dlMsg end try LintShell(macPath) end SelectFile -- start Docker on StartDocker() set dlTitle to "Docker cannot started" set dlMsg to "Something went wrong, could not start Docker!" try tell application appName activate end tell on error display dialog dlMsg buttons ["OK"] with title dlTitle end try end StartDocker -- check if Docker is running on RunScript() set dlTitle to "Docker not running" set dlMsg to "Should Docker started?" if application appName is running then SelectFile() else display dialog dlMsg buttons ["OK", "No"] with title dlTitle if button returned of result is "OK" then StartDocker() end if end if end RunScript RunScript()
😉 just for fun…