Open the lastest downloaded file with bash script - linux

Below is my attempt at this problem. It's a functional script, but I have to specify the application to be used for each file type. Since this information regarding default application must be stored somewhere on Linux / Ubuntu already, how may I access them and incorporate into my script?
Also, can my script be more "elegant" in any way?
Thank you for helping a Bash script beginner! I appreciate any comment.
#!/bin/bash
# Open the latest file in ~/Downloads
filename=$(ls -t ~/Downloads | head -1)
filetype=$(echo -n $filename | tail -c -3)
if [ $filetype == "txt" ]; then
leafpad ~/Downloads/$filename
elif [ $filetype == "pdf" ]; then
evince ~/Downloads/$filename
fi

How do I open a file in its default program - Linux should help you with the first part of your question:
xdg-open ~/Downloads/$filename
As mentioned in other answers, it's best not to trust the output of ls in scripts, especially if you have unusual characters like newlines in your filenames. One way to robustly get a list of filenames in a script is with the find command, and null-delimiting them into a pipe.
So to answer your question with a one-liner:
find ~/Downloads -maxdepth 1 -type f -printf "%C# %p\0" | sort -zrn | { read -d '' ts file; xdg-open "$file"; }
Breaking it down:
The find command lists files in the ~/Download directory, but doesn't descend any deeper into subdirectories. The filenames are printed with the given printf format, which lists a numerical timestamp, followed by a space, followed by a null delimiter. Note the printf format specifiers for find are different to those for regular printf
The sort command numerically sorts (-n) the resulting null-delimited list (-z) by the first field (numerical timestamp). Sort order is reversed (-r) so that the latest entry is displayed first
The read command reads the timestamp and filename of the first file in the list into the ts and file variables. -d '' tells read to use null delimiters.
The file is opened using xdg-open.
Note the read and xdg-open commands are in a curly bracket inline group, so the file variable is in scope for both.

Welcome to bash programming. :-)
First off, I'll refer you to the Bash FAQ. Great resource, lots of tips, perspectives and warnings.
One of them is the classic Parsing LS problem that your script suffers from. The basic idea is that you don't want to trust the output of the ls command, because special characters like spaces and control characters may be represented in a way that doesn't allow you to refer to the file.
You're opening the "last" file, as determined by a sort that the ls command is doing. In order to detect the most recent file without ls, we'll need some extra code. For example:
#!/bin/sh
last=0
for filename in ~/Downloads/*; do
when=$(stat -c '%Y' "$filename")
if [ $when -gt $last ]; then
last=$when
to_open="$filename"
fi
done
xdg-open "$to_open"
The idea is that we'll walk through each file in your Downloads directory and fine the one with the largest timestamp using the stat command. Then open that file using xdg-open, which may already be installed on your system because it's part of a tool set that's a dependency for a number of other applications.
If you don't have xdg-open, you can probably install it from the xdg-utils package which using whatever package management system is around for your Linux distro.
Another possibility is gnome-open, which is part of the Gnome desktop (the libgnome package, to be precise). YMMV. We'd need to know more about your distro and your desktop environment to come up with better advice.
Note that if you do want to continue selecting your application by extension, you might want to consider using a switch instead of a series of ifs:
...
case "${filename##*.}" in
txt)
leafpad "$filename"
;;
pdf)
xdg-open "$filename"
;;
*)
echo "ERROR: can't open '$filename'" >&2
;;
esac

mimeopen might be useful? There's an explanation of Mime types here.
Also - are your filetype extensions always exactly two letters, as the tail -c -3 implies? If they're of variable length, you may want a regular expression instead.

As previously mentioned, xdg-open and mimeopen may be useful and more elegant; from their manpages:
xdg-open opens a file or URL in the user's preferred application. If a URL is provided the URL will be opened in the user's preferred web browser. If a file is provided the file will be opened in the preferred application for files of that type.
[mimeopen] tries to determine the mimetype of a file and open it with the default desktop application. If no default application is configured the user is prompted with an "open with" menu in the terminal.
For more elegance in the original script, replace
filetype=$(echo -n $filename | tail -c -3)
with
filetype=${filename: -3}
and instead of the five-lines if/elif/fi structure, consider using two lines as follows.
[ $filetype == "txt" ] && leafpad ~/Downloads/$filename
[ $filetype == "pdf" ] && evince ~/Downloads/$filename

Related

Converting .aiff to .wav in SoX

I have multiple files in a directory that are in .aiff format, and I would like to convert them to .wav using SoX. I have tried the code on this website, which is as follows
theFiles = `/Users/me/RainbowAiff/*.aiff`;
for eachFile in $theFiles; do v1=${eachFile%*.aiff};
oldFile="$v1.aiff"; newFile = "$v1.wav";
echo oldFile $oldFile; echo newFile $newFile; sox $oldFile $newFile; done
and this website, which is as follows
for i in `/Users/me/RainbowAiff/ *.aiff`; do echo -e "$i"; sox $i $i.wav; echo -e "$i.wav"; done;
but I get an error message in both instances that says "cannot execute binary file". What could be the source of this error?
Incompatible binary files cause this error, see this question.
If the outputs from file sox and uname -a commands tell that there is a discrepancy between the binary file and your operating system, i.e., x86 vs. ARM, you need to find the correct binary for SoX. Although there is no SoX release since 2015, check this and this to find a version that is compatible with your system.
The problem is also related to the file attributes of the sox. In order to give it the executable attribute, simply go to the folder where sox is located and run chmod +x sox command (you need root access first).
The two snippets you linked to looks a little sketchy to me with their use of ls and echo.
echo -e might in fact be what gave you the syntax error, as OSX (BSD) echo does not have an -e option. (type man echo into the terminal to see).
I took the liberty of assembling an alternative one-liner, based around find and its -exec option.
find -E . -maxdepth 1 -iregex '.*\.(aif|aiff)' -exec bash -c '$0 "$1" "${1%.*}.wav"' sox {} \;
To explain it:
find: can find about anything
-E: enables extended regex*
.: starts in current working directory. Can be replaced with relative or absolute path
-maxdepth 1: looks only in the first directory layer. Replace 1 with 2 to also find files within folders, with 3 to also find files within folders within folders …
Removing this option will enable full recursion (find files all the way down)
-iregex: use case-insensitive regular expressions
'.*\.(aif|aiff)': match anything that starts with anything (.) at any length (*), followed by a period (\.) and the strings 'aif' or 'aiff' (or 'AIF', 'AIFF', 'aiFF' …) ((aif|aiff))
-exec bash -c : execute the following in a non-interactive bash shell
Right around here it gets a bit more complicated
'$0 "$1" "${1%.*}.wav"': this is the call to be run inside the bash shell. $0 will expand to the program name. "$1" will expand to the first (and in this case, only) argument, enclosed in double quotes in case of white-spaces. "${1%.*}.wav" will also expand to the first argument, except it will strip away the last period and everything after, before tagging on '.wav' at the end. Effectively replacing the file extension.
sox {}: these are the arguments passed to the shell call. The first (sox) is the program we want to use, referred to as $0 within the call. The second ({}) is whatever file find has found, and referred to as $1 within the shell call
That's quite a mouthful and I'm no expert, so there might be some mistakes in what I've written, though the general outline should be solid.
*This is OSX specific, a less pretty but more portable option would be:
find . -maxdepth 1 \( -iname "*.aif" -o -iname "*.aiff" \) -exec bash -c '$0 "$1" "${1%.*}.wav"' sox {} \;

How to solve this ambiguous redirect error

I want to run a program (anchor) in all the .fa files in a directory, and append the output back into the input files (as next lines of original input content). For that I have tried:
for f in ./*.fa ; do ./anchor $f -d ./; done >> $f
and it gives the error:
bash: $f: ambiguous redirect
I understand bash is objecting to post the output content in the input file, but as I am recently migrating from windows system, there I'm doing it as:
for %F in ("*.fa") do anchor %F -d ./ >> %%F
which gives me the desired output.
Although this might seems strange to append output in input files, but how can I do that in shell?
Thanks
ps. I also try to use $$ in output redirection, but it forms a separate output file with different name and the original input content are also not merged in it.
the most logical way of doing it is to redirect from inside the loop instead, but not directly (thanks 123 for the comment: file cannot be input and output as the same time, maybe here it could work since windows loop seems to work, but let's not take useless risks...)
for f in ./*.fa ; do ./anchor $f -d ./ > /tmp/something; cat /tmp/something >> $f; done
BTW: I wouldn't dare trying to explain what your original code does, is f defined/when $f is evaluated (before or after entering the for loop).
My guess is that $f just not evaluated and considered as $f literally, which confuses bash.
At any rate, it's incorrect.
EDIT: the windows version
for %F in ("*.fa") do anchor %F -d ./ >> %%F
does the redirection inside the loop (unlike your unix attempt), and it's really surprising that it works because of windows file locking...
What could happen (not sure) is that windows doesn't try to append to the file before something is issued on standard output, and at that moment, the program has closed the file as input.

Command line tool to search docx file under ms dos or cygwin

Is there a command line tool that is able to search docx file under ms dos or cygwin ?
I have tried grep, it's not working with docx while working fine with txt file.
I know I could always convert the docx to txt 1st then search using grep, but I am wondering
is there a command tool that I can search directly under command line?
Thanks
i wrote a small bash script, which would help you:
#!/bin/bash
export DOCKEY="$#"
function searchdoc(){
VK1=$(cat "$#" | grep -i "$DOCKEY" | wc -c)
VK2=$(unzip -c "$#" | grep -i "$DOCKEY" | wc -c)
let NUM=$VK1+$VK2
if [ "$NUM" -gt 0 ]; then
echo $NUM occurences in $#
echo opening file.
gnome-open "$#"
fi
}
export -f searchdoc
echo searching for $DOCKEY ...
find . -exec bash -c 'searchdoc "{}" 2>/dev/null' \;
save it as docfind.sh and you can invoke
$#> docfind.sh searchterm
from any folder you want to scan.
After a trying out the stuff , I found the easiest way to do this is to use a linux utility to batch convert all docx files into txt files, then do grep with those txt files easily.
zgrep might work for you? It usually works in OpenOffice documents, and both are compressed archives containing XML:
zgrep "some string" *.xdoc
I have no .xdoc files to test this with, but in theory it should work...
You can use zipgrep, which calls grep on all files of a zip archive (which a docx file is).
You might be disappointed with the result, though, as it returns raw content of XML files containing both the text and XML tags.
save it as docfind.sh and you can invoke
Newbies like me might need to be told that for the .sh script to be executable from any directory, it needs to have the executable property set and be located in /usr/bin or elsewhere in your Path.
I was able to set up the nemo file manager in Linux Mint to open a terminal from any folder's context menu (information here).

less-style markdown viewer for UNIX systems

I have a Markdown string in JavaScript, and I'd like to display it (with bolding, etc) in a less (or, I suppose, more)-style viewer for the command line.
For example, with a string
"hello\n" +
"_____\n" +
"*world*!"
I would like to have output pop up with scrollable content that looks like
hello
world
Is this possible, and if so how?
Pandoc can convert Markdown to groff man pages.
This (thanks to nenopera's comment):
pandoc -s -f markdown -t man foo.md | man -l -
should do the trick. The -s option tells it to generate proper headers and footers.
There may be other markdown-to-*roff converters out there; Pandoc just happens to be the first one I found.
Another alternative is the markdown command (apt-get install markdown on Debian systems), which converts Markdown to HTML. For example:
markdown README.md | lynx -stdin
(assuming you have the lynx terminal-based web browser).
Or (thanks to Danny's suggestion) you can do something like this:
markdown README.md > README.html && xdg-open README.html
where xdg-open (on some systems) opens the specified file or URL in the preferred application. This will probably open README.html in your preferred GUI web browser (which isn't exactly "less-style", but it might be useful).
I tried to write this in a comment above, but I couldn't format my code block correctly. To write a 'less filter', try, for example, saving the following as ~/.lessfilter:
#!/bin/sh
case "$1" in
*.md)
extension-handler "$1"
pandoc -s -f markdown -t man "$1"|groff -T utf8 -man -
;;
*)
# We don't handle this format.
exit 1
esac
# No further processing by lesspipe necessary
exit 0
Then, you can type less FILENAME.md and it will be formatted like a manpage.
If you are into colors then maybe this is worth checking as well:
terminal_markdown_viewer
It can be used straightforward also from within other programs, or python modules.
And it has a lot of styles, like over 200 for markdown and code which can be combined.
Disclaimer
It is pretty alpha there may be still bugs
I'm the author of it, maybe some people like it ;-)
A totally different alternative is mad. It is a shell script I've just discovered. It's very easy to install and it does render markdown in a console pretty well.
I wrote a couple functions based on Keith's answer:
mdt() {
markdown "$*" | lynx -stdin
}
mdb() {
local TMPFILE=$(mktemp)
markdown "$*" > $TMPFILE && ( xdg-open $TMPFILE > /dev/null 2>&1 & )
}
If you're using zsh, just place those two functions in ~/.zshrc and then call them from your terminal like
mdt README.md
mdb README.md
"t" is for "terminal", "b" is for browser.
Using OSX I prefer to use this command
brew install pandoc
pandoc -s -f markdown -t man README.md | groff -T utf8 -man | less
Convert markupm, format document with groff, and pipe into less
credit: http://blog.metamatt.com/blog/2013/01/09/previewing-markdown-files-from-the-terminal/
This is an alias that encapsulates a function:
alias mdless='_mdless() { if [ -n "$1" ] ; then if [ -f "$1" ] ; then cat <(echo ".TH $1 7 `date --iso-8601` Dr.Beco Markdown") <(pandoc -t man $1) | groff -K utf8 -t -T utf8 -man 2>/dev/null | less ; fi ; fi ;}; _mdless '
Explanation
alias mdless='...' : creates an alias for mdless
_mdless() {...}; : creates a temporary function to be called afterwards
_mdless : at the end, call it (the function above)
Inside the function:
if [ -n "$1" ] ; then : if the first argument is not null then...
if [ -f "$1" ] ; then : also, if the file exists and is regular then...
cat arg1 arg2 | groff ... : cat sends this two arguments concatenated to groff; the arguments being:
arg1: <(echo ".TH $1 7date --iso-8601Dr.Beco Markdown") : something that starts the file and groff will understand as the header and footer notes. This substitutes the empty header from -s key on pandoc.
arg2: <(pandoc -t man $1) : the file itself, filtered by pandoc, outputing the man style of file $1
| groff -K utf8 -t -T utf8 -man 2>/dev/null : piping the resulting concatenated file to groff:
-K utf8 so groff understands the input file code
-t so it displays correctly tables in the file
-T utf8 so it output in the correct format
-man so it uses the MACRO package to outputs the file in man format
2>/dev/null to ignore errors (after all, its a raw file being transformed in man by hand, we don't care the errors as long as we can see the file in a not-so-much-ugly format).
| less : finally, shows the file paginating it with less (I've tried to avoid this pipe by using groffer instead of groff, but groffer is not as robust as less and some files hangs it or do not show at all. So, let it go through one more pipe, what the heck!
Add it to your ~/.bash_aliases (or alike)
I personally use this script:
#!/bin/bash
id=$(uuidgen | cut -c -8)
markdown $1 > /tmp/md-$id
google-chrome --app=file:///tmp/md-$id
It renders the markdown into HTML, puts it into a file in /tmp/md-... and opens that in a kiosk chrome session with no URI bar etc.. You just pass the md file as an argument or pipe it into stdin. Requires markdown and Google Chrome. Chromium should also work but you need to replace the last line with
chromium-browser --app=file:///tmp/md-$id
If you wanna get fancy about it, you can use some css to make it look nice, I edited the script and made it use Bootstrap3 (overkill) from a CDN.
#!/bin/bash
id=$(uuidgen | cut -c -8)
markdown $1 > /tmp/md-$id
sed -i "1i <html><head><style>body{padding:24px;}</style><link rel=\"stylesheet\" type=\"text/css\" href=\"http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css\"></head><body>" /tmp/md-$id
echo "</body>" >> /tmp/md-$id
google-chrome --app=file:///tmp/md-$id > /dev/null 2>&1 &
I'll post my unix page answer here, too:
An IMHO heavily underestimated command line markdown viewer is the markdown-cli.
Installation
npm install markdown-cli --global
Usage
markdown-cli <file>
Features
Probably not noticed much, because it misses any documentation...
But as far as I could figure out by some example markdown files, some things that convinced me:
handles ill formatted files much better (similarly to atom, github, etc.; eg. when blank lines are missing before lists)
more stable with formatting in headers or lists (bold text in lists breaks sublists in some other viewers)
proper table formatting
syntax highlightning
resolves footnote links to show the link instead of the footnote number (not everyone might want this)
Screenshot
Drawbacks
I have realized the following issues
code blocks are flattened (all leading spaces disappear)
two blank lines appear before lists

Help needed to nab the malware viral activity using awk

I am facing issues with my server as sometimes the malwares are adding their code at the end or start of the files. I have fixed the security loopholes to the extent of my knowledge. My hosting provider has informed that the security is adequate now, but I have become paranoid with the viral/malware activity on my site. I have a plan, but I am not well versed with Linux editors like sed or awk or gawk so help needed from your side. I can do this using my PHP knowledge but that would be very resource intensive.
Since malwares/virus add code at the start or end of the file (so that the website does not show any error), can you please let me know how to write a command which would recursively look into all .php files (I will use the help to make changes in other type of files) in parent and all sub-directories and add a particular tag at the start and end of the file, say, XXXXXX_START, and YYYYYY_END.
Then I need a script which would read all the .php files and check if the first line of the code is XXXXX_START and last line is YYYYYYY_END and create a report if any file is found to be different.
I will setup a cron to check all the files and email the report to me if any discrepancy found.
I know this is not 100% foolproof as virus may add the data after the commented lines, but this is the best option I could think of.
I have tried the following commands to add data at the start -
sed -i -r '1i add here' *.txt
but this isn't recursive and it adds line to only the parent directory files.
Then I found this -
BEGIN and END are special patterns. They are not used to match input records. Rather, they are used for supplying start-up or clean-up information to your awk script. A BEGIN rule is executed, once, before the first input record has been read. An END rule is executed, once, after all the input has been read. For example:
awk 'BEGIN { print "Analysis of `foo'" }
/foo/ { ++foobar }
END { print "`foo' appears " foobar " times." }' BBS-list
But unfortunately, I could not decipher anything.
Any help on above mentioned details is highly appreciated. Any other suggestions are welcomed.
Regards,
Nitin
You can use the following to modify the files (also creates backup files called .bak):
find . -name "*.php" | xargs sed -i.bak '1iSTART_XXXX
$aEND_YYYY'
You could use the following shell script for checking the files:
for f in `find . -name "*.php" -print`
do
START_LINE=`head -1 $f`
END_LINE=`tail -1 $f`
if [[ $START_LINE != "START_XXXX" ]]
then
echo "$f: Mismatched header!"
fi
if [[ $END_LINE != "END_YYYY" ]]
then
echo "$f: Mismatched footer!"
fi
done
Use version control and/or backups; in the event of suspicious activity, zap the live site and reinstall from backups or your version control source.
$ find . -type f | grep "txt$" | xargs sed -i -r '1i add here'
Will apply that command to all files in or under the current directory. You could probably fold the grep logic into find, but I like simple incantations.

Resources