I have a content of compiled Java class as a binary file (named X.class). Let's assume it has some class inside with a proper main method, but I don't know original class name (and it's not X).
How can I run it with java using single command line?
You can get the name of the class using javap -c <filename>. From that point on, it's just shell games. This works on OS X:
java $(javap -c File.class | head -n 2 | tail -n1 | cut -f3 -d\ )
Note the space after the \ in the subshell - this is requires to isolate the class name.
Update based on #nikolay-vyahhi feedback:
If you're not sure at which position the class name will appear (e.g. when it's unknown whether the class is public or not), you can use tr and grep, like so:
java $(javap -c File.class | head -n2| tail -n1 | tr [:space:] \\n | grep -A1 "class\|interface" | tail -n1)
You can use javap to do that:
javap X.class
It will disassemble the class and print out the package, protected, and public fields and methods:
Compiled from "Test.java"
public class Test {
public Test();
public static void main(java.lang.String...);
}
Then you can use this to run the application:
java $(javap X.class | sed -n 's/.*class \(.*\) {/\1/p')
Related
In the AWS re:invent presentation on Lambda performance (highly recommend) on pp.33-34 the author lists the count of classes loaded within each library using the following command:
java -cp my.jar -verbose:class Handler | grep '\[Loaded' | grep '.jar\]' | sed -r 's/\[Loaded \([^A-Z]*\)[\$A-Za-z0-9]*from.*\]/\1/g' | sort | uniq -c | sort
This basically extracts the namespace up to but not including the first capital letter, which is the class name. The output is supposed to look something like this:
143 com.fasterxml.jackson
219 org.apache.http
373 com.google
507 com.amazonaws
However this only works with the Java 8 class loader logs, which have the following format (this example should output java.io):
[Loaded java.io.Serializable from shared objects file]
The class loader logs as of Java 9+ have this different format:
[0.041s][info][class,load] java.io.Serializable source: jrt:/java.base
How does the sed command need to be updated to produce the same output as above?
I've tried the following, but the entire line is extracted in the regex group, not just the class library. I'm also running on a Mac, so I had to add a -r flag and remove some of the escape characters:
java -cp my.jar -verbose:class Handler | grep '[class,load]' | grep '.jar' | sed -r 's/.*\[class,load\] ([^A-Z]*)[$A-Za-z0-9]*source.*/\1/g'
Since the record has fields space separated we can take advantage of cut to get the desired field and then use sed to extract the package substring. The ([a-z.]+)\.[A-Z].* regex looks for lower case letters and dots until the first dot followed by an upper case letter.
echo "[0.041s][info][class,load] java.io.Serializable source: jrt:/java.base" | cut -d ' ' -f2 | sed -E 's/([a-z.]+)\.[A-Z].*/\1/g'
Result:
java.io
If a sed only solution is preferred this command will do grep and cut jobs as well:
echo "[0.041s]..." | sed -nE '/class,load/ s/[^ ]+ ([^ ]+)/\1/ ; s/([a-z.]+)\.[A-Z].*/\1/p'
grep : /class,load/
cut : s/[^ ]+ ([^ ]+)/\1/
extract: s/([a-z.]+)\.[A-Z].*/\1/p
I have two files in the same directory namely Main.java and Functions.java
The content of Main.java is this:
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
String s = scan.next();
Functions you = new Functions(s);
System.out.println("Your name is " + you);
}
}
And Functions.java:
public class Functions {
public String name;
public Functions(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
I manually compile and run them on my terminal. So this is what I did:
javac folder1/folder2/*.java
And after
java folder1/folder2/Main.java
But it gives me an error of this:
error: cannot find symbol
Functions you = new Functions(s);
symbol: class Functions
Even though they are on the same directory, it will still give an error.
Edit: I interchanged my code for compiling and running. Sorry for that.
run javac Main.java
run java Main
Note 1: You don't need to explicitly compile the files which are being used in the file you're compiling. If Java compiler sees, that you're using class B in the class A, Java will implicitly compile all the classes (including class B) when compiling the container class (class A, in this case).
Note 2: If you're using JDK >=11, then you can just compile+run the program with one command. JEP 330, Launch Single-File Source-Code Programs, is one of the new features introduced in the JDK 11 release. So, you can just run java ClassName.java.
The issue is classpath.
folder1
| folder2
| | Main.java
| | Functions.java
After you run javac folder1/folder2/*.java you'll have class files.
folder1
| folder2
| | Main.class
| | Main.java
| | Functions.class
| | Functions.java
Since your classes are not in a package then folder2 needs to be on the classpath. To run main.
java -cp folder1/folder2 Main
Notice that I left off the .java on the name of the class.
If you don't want to supply the -cp argument (sets the classpath) you can cd folder1/folder2 then you'll be in the folder with the class files. You can just type.
java Main
I tried this with your java files. When it works you won't see any output until you press enter.
Given that Java 9 is upon us and we can finally have a java REPL with jshell I was hoping there was a way to add a shebang to a script and have jshell interpret it.
I tried creating test.jsh:
#!/usr/bin/env jshell -s
System.out.println("Hello World")
/exit
However that gives:
⚡ ./test.jsh
| Error:
| illegal character: '#'
| #!/usr/bin/env jshell -s
| ^
| Error:
| illegal start of expression
| #!/usr/bin/env jshell -s
| ^
Hello World
It turns out there is an enhancement request for this in OpenJDK https://bugs.openjdk.java.net/browse/JDK-8167440.
Is there any other way to do this?
Use
//usr/bin/env jshell --show-version --execution local "$0" "$#"; exit $?
as the first line of test.jsh. The test.jsh script could look like:
//usr/bin/env jshell --show-version "$0" "$#"; exit $?
System.out.println("Hello World")
/exit
The command line option --show-version is optional, of course, but gives immediate feedback that the tool is running.
The extra command line option --execution local prevents jshell to spawn another VM. This speeds up launch time and if an exception is thrown by your script code, the local VM will exit.
Consult the output of jshell --help and jshell --help-extra for more options.
Update
Also take a look at https://github.com/jbangdev/jbang Having fun with Java scripting, which offers a neat wrapper around running .java files from the command line.
It turns out that with a bit of trickery there is a way, although I haven't fully managed to suppress the interpreted commands but pretty close to what I want.
Change test.jsh to:
#!/usr/bin/env sh
tail -n +4 "$0" | jshell -s "$#"
exit $?
System.out.println("Hello World")
/exit
Which gives us:
⚡ ./test.jsh
-> System.out.println("Hello World")
Hello World
-> /exit
Inspired by steiny answer, I came up with a more generic solution
https://gist.github.com/ffissore/012d7e32a096fde5266f49038c93dcaf
In essence: jshell-wrapper will strip the first line of the script (which is supposed to be the shebang) and will add a /exit at the end of the script
The below works too; put it into a someScript.jsh file and run it with ./someScript.jsh. All arguments received by someScript.jsh will go to String[] args.
#!/home/gigi/.sdkman/candidates/java/current/bin/java --source 11
import java.util.Arrays;
import ro.go.adrhc.*; // example of using your classes, e.g. App below
public class X {
public static void main(String[] args) {
// do whatever you want here, e.g.:
// System.out.println("Hello World");
// or
// use a class:
// App.main(args);
// e.g. from ro.go.adrhc package, by running:
// CLASSPATH="/path-to-ro.go.adrhc-classes" ./someScript.jsh
}
}
The usage of the wrapping class, here X, is a mandatory trick for this to work. Use the Java version you have by changing /home/gigi/.sdkman/candidates/java/current/bin/java.
Inspired by https://blog.codefx.org/java/scripting-java-shebang/.
I'd like to run an entire file with JShell like:
$ jshell my-jshell-skript.java
Where e.g. the content of my my-jshell-skript.java is 40 + 2;.
Or alternatively an executable like:
#!/usr/bin/jshell
40 + 2
Is this possible now or do I still have to take the old way over a Java-Main-Class?
Edit 1: Windows-Problem
On Windows, there is still no solution for me:
C:\JDKs\jdk9.0.0.0_x64\bin>type foo.jsh
1 + 1
C:\JDKs\jdk9.0.0.0_x64\bin>jshell.exe foo.jsh
| Welcome to JShell -- Version 9
| For an introduction type: /help intro
jshell> /exit
| Goodbye
C:\JDKs\jdk9.0.0.0_x64\bin>
JShell starts ignoring my file completely. Is it a bug?
Edit 2: Solution for Windows-Problem
Turns out that it is the content of my foo. Seems like 1 + 1 does only work "on the fly", not read from a file:
C:\JDKs\jdk9.0.0.0_x64\bin>type foo.jsh
System.out.println("foo");
C:\JDKs\jdk9.0.0.0_x64\bin>jshell.exe foo.jsh
foo
| Welcome to JShell -- Version 9
| For an introduction type: /help intro
jshell> /exit
| Goodbye
C:\JDKs\jdk9.0.0.0_x64\bin>
You can create a Jshell script file named some.jsh with those statements and on the command prompt from where you run jshell, execute it as:-
jshell /path/to/some.jsh
On a MacOSX, I would do something like:
You can pipe the string to JShell:
echo 1 + 2 | jshell
Example:
:/# echo 1 + 2 | jshell
| Welcome to JShell -- Version 9
| For an introduction type: /help intro
jshell> 1 + 2
$1 ==> 3
:/#
Or, from a file:
cat myfile | jshell
Where myfile contains the line "1 + 2".
JShell is not meant to run a Java class directly. If you want to run a java class, you still need to do it the old way - java <your-class-name>.
From the docs,
The Java Shell tool (JShell) is an interactive tool for learning the
Java programming language and prototyping Java code. JShell is a
Read-Evaluate-Print Loop (REPL), which evaluates declarations,
statements, and expressions as they are entered and immediately shows
the results.
As per this quote, JShell is meant for running or trying out individual Java statements. In the traditional java way, you have to write a full Java program before you can run it and see the results. But JShell allows you a way to try out the Java statements without needing you to build the full standalone java application.
So the short answer to your question is that, no, you can't call standalone java applications like jshell my-jshell-skript.java. However, you CAN call a script file which contains individual JShell commands or Java statements. So if you copy all the statements from your Java program and paste them to a JShell script, you can run the script like:
% jshell my-jshell-skript.jsh
But this is not quite the same as running a standalone java application.
Launch jshell in concise feedback mode and filter the required content-
$echo '40 + 2' | jshell --feedback concise | sed -n '2p' |sed -En 's/[^>]*>(.+)/\1/gp'
output: 42
More details here- How to execute java jshell command as inline from shell or windows commandLine
In jshell you can save the current snippets into a file by issuing:
/save Filename
Likewise, you can load the file into the current context/session by issuing:
/open Filename
Here is one such example:
| Welcome to JShell -- Version 9.0.7.1
| For an introduction type: /help intro
jshell> String[] names={"nameone","nametwo"}
names ==> String[2] { "nameone", "nametwo" }
jshell> Arrays.toString(names);
$2 ==> "[nameone, nametwo]"
jshell> /save myExample
jshell> % sudipbhandari at sysadm-Latitude-5480 in ~ 18:22
> jshell
| Welcome to JShell -- Version 9.0.7.1
| For an introduction type: /help intro
jshell> names
| Error:
| cannot find symbol
| symbol: variable names
| names
| ^---^
jshell> /open myExample
jshell> names
names ==> String[2] { "nameone", "nametwo" }
In Windows, to see the verbose output for a jsh file
type file.jsh | jshell -v
Problem when running jshell file.jsh
D:\>type file.jsh
3 + 5
D:\>jshell file.jsh
| Welcome to JShell -- Version 13.0.2
| For an introduction type: /help intro
jshell>
Workaround:
D:\>type file.jsh
3 + 5
D:\>type file.jsh | jshell -v
| Welcome to JShell -- Version 13.0.2
| For an introduction type: /help intro
jshell> $1 ==> 8
| created scratch variable $1 : int
jshell>
Note: file should contain a blank line (/n) after the last line, else the last line is not getting executed
Pipe usage can be achieved with the "hyphen" option, absent in the initial jshell release.
echo 'System.out.print(1 + 2)' | jshell -
https://docs.oracle.com/en/java/javase/11/tools/jshell.html
https://bugs.openjdk.java.net/browse/JDK-8187439
Is it possible to generate a global call graph of an application?
Basically I am trying to find the most important class of an application.
I am looking for options for Java.
I have tried Doxy Gen, but it only generates inheritance graphs.
My current script:
#! /bin/bash
echo "digraph G
{"
find $1 -name \*.class |
sed s/\\.class$// |
while read x
do
javap -v $x | grep " = class" | sed "s%.*// *%\"$x\" -> %" | sed "s/$1\///" | sed "s/-> \(.*\)$/-> \"\1\"/"
done
echo "}"
javap -v and a bit of perl will get you dependencies between classes. You can make your parser slightly more sophisticated and get dependencies between methods.
Update: or if you have either a *nix or cygwin you can get a list of dependencies as
find com/akshor/pjt33/image -name \*.class |
sed s/\\.class$// |
while read x
do
javap -v $x | grep " = class" | sed "s%.*// *%$x -> %"
done
Add a header and a footer and you can pass it to dot to render a graph. If you just want to know which classes are used by the most other classes, as your question implies, then
find com/akshor/pjt33/image -name \*.class |
sed s/\\.class$// |
while read x
do
javap -v $x | grep " = class" | sed "s%.*// *%%"
done |
sort | uniq -c | sort -n
For advanced code analysis you might wanna have a look at http://www.moosetechnology.org/
Cheers
Thomas
(edit: moved down here by general request, See: How to generate a Java call graph)