PDA

View Full Version : Bash Scripting open command



xmarkx
08-24-2014, 01:52 PM
Hello i am not pro in bash scripting so I am creating a command in Linux that cna open specific files accordin to tis extension.

Can anyone help me out? I need to make this code more professional looking. Any suggestions? I want to place maybe 10 more elif blocks...


1 #!/bin/bash
2 #
3 # Script created by Tzivaras Vasilis
4 # Open any file from a console according to the file extension
5 #
6 # Bug: Fix filename spaces (does not open these files)
7 #
8
9 # Get rid of warnings
10 export NO_AT_BRIDGE=1
11
12 targetFile=$1;
13 extension=null;
14
15 echo $1 | awk -F'.' '{ print $2 }' > ~/.tempFile
16
17 file="/home/xmarkx/.tempFile"
18
19 extension=`cat $file`
20
21 if [ "$extension" = "png" ]; then
22 eog $targetFile;
23 elif [ "$extension" = "jpg" ]; then
24 eog $targetFile;
25 elif [ "$extension" = "pdf" ]; then
26 evince $targetFile;
27 else
28 echo "Extension is not recognized yet!";
29 fi
30
31 rm /home/xmarkx/.tempFile

Nominal Animal
08-24-2014, 03:59 PM
targetFile=$1;
To support all possible file names, you should always quote the file names, i.e. use targetFile="$1" here. Bash does not need semicolons at end of commands, either.



echo $1 | awk -F'.' '{ print $2 }' > ~/.tempFile
file="/home/xmarkx/.tempFile"
extension=`cat $file`

This extracts everything after the first dot, and in a very convoluted manner (while not quoting the file name argument, thus exposing it to shell globbing and file name expansion). A much easier way is to use string expansion:

extension="${1##*.}"
which stores the first positional parameter, with everything until the last dot removed, to shell variable extension. For example, if the first positional parameter to the script was /home/xmarkx/.secrets/not-really/not-safe-for-work.flv, then extension will contain .flv.

(The ## operator means "remove the longest matching initial part, if any", and the pattern that follows it is a glob pattern (http://en.wikipedia.org/wiki/Glob_pattern) -- just like those you use with file names. They're very common in shell commands. Bash and other utilities do also support regular expressions (http://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended) too, but although they may look a bit similar, they have very different rules.)

Finally, the if .. elif .. else .. fi construct is a poor choice for this; you really should use case (http://www.gnu.org/software/bash/manual/bashref.html#index-case) instead. It uses glob pattern matching, too, and you can list more than one matching case by separating the cases with the pipe character(|).

Personally, I'd write the script quite a bit differently:


#!/bin/sh
if [ -z "$*" ] || [ "$*" = "-h" ] || [ "$*" = "--help" ]; then
exec >&2
echo ""
echo "Usage: $0 [ -h | --help ]"
echo " $0 FILE [ FILE .. ]"
echo ""
echo "This utility determines the type of each FILE, and"
echo "launches the appropriate viewer on the background."
echo ""
exit 0
fi

for file in "$@" ; do

# Check the file name:
case "$file" in

*.[Pp][Dd][Ff])
evince "$file" &
continue
;;

*.[Gg][Ii][Ff]|*.[Pp][Nn][Gg]|*.[Jj][Pp][Gg]|*.[Jj][Pp][Ee][Gg])
display "$file" &
continue
;;

*.[Ss][Vv][Gg]|*.[Ss][Vv][Gg][Zz]|*.[Ss][Vv][Gg].[Gg][Zz])
display "$file" &
continue
;;

esac
# If we get this far, we didn't recognize the file name.

# Run "file", to determine the file type, using the POSIX locale.
if ! type="$(LANG=C LC_ALL=C file "$file")" ; then
echo "$file: Cannot open file." >&2
continue
fi

# Check for known file types.
case "$type" in

*PDF\ Document*)
evince "$file" &
continue
;;

*\ image\ data*)
display "$file" &
continue
;;

*\ SVG\ Scalable\ Vector\ Graphics*)
display "$file" &
continue
;;

esac
# None of the "file" utility output patterns matched either.

echo "$file: Unknown file type." >&2
done

The if clause on the second line checks if there is any command-line arguments, or just a -h or a --help, and if so, outputs the usage info to standard error.

I want my script to launch one utility per file I specify, so I use a loop (over all command-line parameters supplied to the script). Note that "$@" expands correctly, as if each separate parameter was quoted separately, regardless of the characters in the file names.

The continue command skips the rest of the loop body, and goes to the next parameter (if any).

Because I did not quote the patterns, I use a backslash to escape the spaces when they are part of the pattern (\ ). Otherwise the shell gets confused -- it sees multiple tokens where it expected a pattern (or multiple patterns separated with pipe characters,|).

The if ! type="$(command...)" ; then syntax runs command..., saving its output to variable type. If the command... has a nonzero exit status, then the then part is executed. (If you omit the exclamation point, !, then the then part is executed only if command... has a zero (success) exit status.)

Since we're not using any bashisms, I'm using #!/bin/sh instead; on many systems, it resolves to dash, which tends to have a smaller start-up latency compared to bash; but as it is a POSIX shell (http://pubs.opengroup.org/onlinepubs/009604599/utilities/xcu_chap02.html), it does not contain all the bells and whistles bash does.

Questions?

xmarkx
08-25-2014, 11:41 AM
Um, I ll study all these stuff and come back. There are quite a few things that i do not understand but i dont want to throw all my newbies questions here :| Thanks for the time writing all these stuff m8

xmarkx
08-25-2014, 03:06 PM
Ok first of all i dont understand why you use for loop. Also what does that mean ">&2"? Lstly i dont get these lines like

*.[Pp][Dd][Ff])

My new version is this one:

#!/bin/bash
#
# Script created by Tzivaras Vasilis
# Open any file from a console according to the file extension
#

# If no arguments are given they echo the usage and details about the
# command
if [ -z "$*" ] || [ "$*" = "-h" ] || [ "$*" = "--help" ]; then
exec >&2
echo ""
echo "Usage: $0 [ -h | --help ]"
echo " $0 FILE [ FILE .. ]"
echo ""
echo "This utility determines the type of each FILE, and"
echo "launches the appropriate viewer on the background."
echo ""
exit 0
fi

# Get rid of warnings
export NO_AT_BRIDGE=1

# Support all filenames
targetFile="$1";

extension="${1##*.}"

case "$extension" in
png|jpg|bmp)
eog $targetFile;
;;
pdf)
evince $targetFile;
;;
txt)
gedit $targetFile;
;;
*)
echo ""
echo " Sorry, this type of files are not supported yet :'("
echo " please report this extension to vtzivaras@gmail.com"
echo ""
;;
esac

I really hope it is better :)

Nominal Animal
08-25-2014, 04:49 PM
Ok first of all i dont understand why you use for loop.

I want my script to launch a viewer for each file I specify, not just the first one.


Also what does that mean ">&2"?
The command

exec >&2
redirects standard output to standard error, from that point forwards, until the script exits. You can omit it if you want.

You see, many scripts produce the "useful" output to standard output, and error messages (and in my case, usage too) to standard error. While both appear on the terminal screen by default, the user can redirect the output to a file (by adding > tofile at the end of the command). If error messages are directed to standard error using >&2, then those will appear on the terminal even if the output was redirected.

I myself want the script usage (or help) to go to standard error, too. I do not bother to memorize details like rarely-used script parameter formats; having both errors and the usage go to standard error just fits in my workflow much better. Others prefer otherwise.

Lstly i dont get these lines
As you probably realized, my case statement uses the full file name; I didn't bother separating the filename extension at all. In glob patterns, [Jj] means either uppercase or lowercase letter J. You can even use a dash for ranges: [0-9A-Za-z] matches exactly one digit or uppercase or lowercase letter.

For example, *.[Gg][Ii][Ff] means "match anything followed by a dot (.), followed by an upper- or lowercase G, followed by an upper- or lowercase i, followed by an upper- or lowercase F".

Although my own file names are almost always in lower case, sometimes -- for example, when downloading images from a digital camera, or extracting e-mail attachments -- the file names are in mixed case, and the extension can be uppercase too.

If you want your script to handle uppercase and mixed-case filename extensions (like Gif or JPEG), in Bash you can add

extension="${extension,,[A-Z]}"
at line 28 (just after the first extension assignment).

Because your version only views the filename specified as the first parameter, you might wish to use


if [ $# -ne 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
exec >&2
echo ""
echo "Usage: $0 [ -h | --help ]"
echo " $0 FILE"
echo ""
echo "This utility determines the type FILE, and launches"
echo "the appropriate viewer on the background."
echo ""
exit 0
fi

The above test checks if there are not exactly one command-line parameter ($# yields the number of positional parameters), or if the parameter is -h or --help, and if so, outputs the usage. I think the if clause is easier to understand this way, and the help text should reflect that only one filename is accepted.

Note that you forgot the double quotes from lines 31, 34, and 37; you should use the double quotes to make sure the application gets the file name as a single parameter, even if it contains spaces or other weird characters. (In other words, use eog "$targetFile" and so on.) There is no need for the single semicolon at the end of the lines in shell scripts, as I mentioned in my earlier post.

The ;; is the separator for the different case statements, but I think you already realized that.

As it is now, your script executes the viewer, but stays in memory until after the viewer exits. (You can see if you run ps axfu while running your command; it shows the viewer program is a child process of the script.)
If you want, you can do a very small modification, that makes the script exit as soon as the viewer is launched -- actually, technically the viewer replaces the script process. All you need to do is add exec before the command -- in other words, exec eog "$targetFile" for example.

(I know exec use looks a bit confusing, but it really isn't: it has nothing to do with running something else; it just means "in this process". So, if you use it with a redirection like I did with the standard error, it means that in the script process, everything from that point forwards is redirected. If you use it with a command, it means the command replaces the current process -- the script execution ends if the command can be executed.)


I really hope it is better

It is! Except for the three lines (31, 34, 37) where you missed the double quotes (which means trouble with filenames with spaces), the rest of my comments in this post is just fine-tuning. :)

In general: There is always room for improvement, and I (and many other members here) could offer quite a few improvements to even my own script in post #2 (http://cboard.cprogramming.com/linux-programming/164070-bash-scripting-open-command.html#post1210209); discussing and discovering such improvements and fixes and suggestions should be considered a positive thing -- even fun, in my opinion. Do not consider the absence of social niceties like explicit encouragement, as discouragement. We just tend to focus on the problem at hand instead of the social stuff.

Adding the request for improvements/suggestions in the error path as you did in your script, is a very good and commendable idea. I think it shows you have a very good (productive!) attitude towards development. Good work, and good luck!

Any further questions?

xmarkx
08-25-2014, 07:01 PM
After some thinking yep it is awesome to multi open files with a loop. I just had the init thought that i could open "multi" files but only each one per time. Anyway, I dont want to make this too complicated right now but i strongly believe that your way will be a version 2 someday. So thanks for that.

Um, I don' t usually use std error with redirecting etc caz in my codes (Java, C etc) if there is a problem i throw an exception or print a message for the user. Um, since i dont know much about bash programming i am really not sure how much helpfull this is and how bad the my code will be if I dont put it at all. But in this version i think its not a big problem. So I ll keep it at the background of my mind for later.

Handling upercase is a "MUST" so yes that is something that I will change. BAsically the first core problem and thought that I ahve to make is handling different files. With spaces, caps, fots, etc. This is my point of view.

Oh, I have to quote every var? Ook done. Also the semi was trash in this file :P I forgot them caz 6 years the only rule is "DO NOT FORGET THE ;" :P Damn languages ...

I used to write a switch case having "break" for separating them but i think ;; means the word 'break' in bash.

Lastly, about the exec, no :) I totally understand it. I have programming in C about exec, wait etc and i am really happy at this point caz other knowledge help me understand very clearly some blocks here. I want to ask if I need to exit or now after the exec. I am not sure about that :/

Anyway, I appreciate your time my friend and really thank you for writing down all these stuff :) I have also pushed it in my github acc and I am tryind to create different versions watching my mistakes so I think and I hope this 'project' will provide me quite a lost of knowledge.