Wayne
Wayne

Reputation: 984

How to break down a bash script into minimal work chunks using Java

I want to write a Java method which reads in a bash script into a single String object, then break that String down into minimal array of String commands so that I can execute them one command at a time. The problem I'm having is that I need to keep the block statements (eg if and while and input redirects) intact, that is, not to break them down further.

As an example, the following bash script:

./configure --sbindir=/lib/security \
            --docdir=/usr/share/doc/Linux-PAM-1.1.3 \
            --enable-read-both-confs &&

make

install -v -m755 -d /etc/pam.d &&

cat > /etc/pam.d/other << "EOF"
auth     required       pam_deny.so
account  required       pam_deny.so
password required       pam_deny.so
session  required       pam_deny.so
EOF

rm -rfv /etc/pam.d

make install &&
chmod -v 4755 /lib/security/unix_chkpwd &&
mv -v /lib/security/pam_tally /sbin &&
mv -v /lib/libpam{,c,_misc}.la /usr/lib &&
sed -i 's| /lib| /usr/lib|' /usr/lib/libpam_misc.la &&
if [ -L /lib/libpam.so ]; then
   for LINK in libpam{,c,_misc}.so; do
       ln -v -sf ../../lib/$(readlink /lib/${LINK}) /usr/lib/${LINK} &&
       rm -v /lib/${LINK}
   done
fi

echo done

I want to programmatically break it down to


./configure --sbindir=/lib/security --docdir=/usr/share/doc/Linux-PAM-1.1.3 --enable-read-both-confs

make

install -v -m755 -d /etc/pam.d

cat > /etc/pam.d/other << "EOF"
auth     required       pam_deny.so
account  required       pam_deny.so
password required       pam_deny.so
session  required       pam_deny.so
EOF

rm -rfv /etc/pam.d

make install

chmod -v 4755 /lib/security/unix_chkpwd

mv -v /lib/security/pam_tally /sbin

mv -v /lib/libpam{,c,_misc}.la /usr/lib

sed -i 's| /lib| /usr/lib|' /usr/lib/libpam_misc.la

if [ -L /lib/libpam.so ]
 then
   for LINK in libpam{,c,_misc}.so; do
       ln -v -sf ../../lib/$(readlink /lib/${LINK}) /usr/lib/${LINK} &&
       rm -v /lib/${LINK}
   done
fi

echo done

Can this be done using regular expression or by some other means using Java?

Even some pseudo code would be welcome.

Upvotes: 0

Views: 213

Answers (3)

Wayne
Wayne

Reputation: 984

This is what I got so far. I have not tested it exhaustively.

String compoundCommand = null;
ArrayList<String> commandList = new ArrayList<String>();
String list = "";
int count = 0;

ArrayList<String> splitBashScript(String script) {
    script = script.replaceAll("\\\\\n", "");
    script = script.replaceAll("([^;]);([^;])", "$1\n$2");
    String[] lines = Pattern.compile("[ \t]*\n", Pattern.MULTILINE).split(script);
    String delimiter = null;
    for (String line : lines) {
        if (!line.isEmpty()) {
            if (compoundCommand == null) {
                if (line.matches(".*<<.*")) {
                    compoundCommand = "here";
                    delimiter = line.replaceFirst(".*<< *", "").replaceAll("\"", "");
                    list += line;
                } else if (line.matches("[ \t]*for[ \t]*.*")) {
                    compoundCommand = "for";
                    count++;
                    list += line;
                } else if (line.matches("[ \t]*select[ \t]*.*")) {
                    compoundCommand = "select";
                    count++;
                    list += line;
                } else if (line.matches("[ \t]*case[ \t]*.*")) {
                    compoundCommand = "case";
                    count++;
                    list += line;
                } else if (line.matches("[ \t]*if[ \t]*.*")) {
                    compoundCommand = "if";
                    count++;
                    list += line;
                } else if (line.matches("[ \t]*while[ \t]*.*")) {
                    compoundCommand = "while";
                    count++;
                    list += line;
                } else if (line.matches("[ \t]*until[ \t]*.*")) {
                    compoundCommand = "until";
                    count++;
                    list += line;
                } else if (list.isEmpty()) {
                    commandList.add(line.replaceFirst("[ \t]*&&$", ""));
                }
            } else if (compoundCommand.equals("here")) {
                list += "\n" + line;
                if (line.matches(delimiter)) {
                    compoundCommand = null;
                    commandList.add(list.replaceFirst("[ \t]*&&$", ""));
                    list = "";
                }
            } else if (compoundCommand.equals("for")) {
                compound(line, "(for|select|while|until)", "done");
            } else if (compoundCommand.equals("select")) {
                compound(line, "(for|select|while|until)", "done");
            } else if (compoundCommand.equals("case")) {
                compound(line, "case", "esac");
            } else if (compoundCommand.equals("if")) {
                compound(line, "if", "fi");
            } else if (compoundCommand.equals("while")) {
                compound(line, "(for|select|while|until)", "done");
            } else if (compoundCommand.equals("until")) {
                compound(line, "(for|select|while|until)", "done");
            }
        }
    }
    return commandList;
}

void compound(String line, String start, String end) {
    list += "\n" + line;
    if (line.matches("[ \t]*" + start + "[ \t]*.*")) {
        count++;
    }
    if (line.matches("[ \t]*" + end + "[ \t]*.*")) {
        count--;
    }
    if (count == 0) {
        compoundCommand = null;
        commandList.add(list.replaceFirst("[ \t]*&&$", ""));
        list = "";
    }
}

Upvotes: 0

James Anderson
James Anderson

Reputation: 27478

You really need a parser!

Have a look at this project jbash which sounds very similar to what you want to do.

They seem to have built all the basic components you will need!

Upvotes: 0

Mat
Mat

Reputation: 206831

Regular expressions are not enough for this, you would need to parse the syntax properly to get something that works reliably.

Parsing shell scripts is not easy at all (no formal grammar AFAIK).

And once you're done parsing, executing "block by block" will generally not work. You'll need to keep track of environment variable changes, current directory changes, etc...

That being said, have you looked at things like jbash?

Upvotes: 1

Related Questions