Trying to run a function in the Bash shell gives unexpected results - bash

I have been trying to batch convert a bunch of really old MS office files to odf formats for archival purposes, using libreoffice from the command line. For this purpose I first gather all the files in a single directory and then invoke the following command (for doc files) within said directory:
/path/to/soffice --headless --convert-to odt *doc
This works well, and the command results in all doc files within the directory being converted in one go. I want to however avoid having to always type out the path to soffice with the necessary parameters, so I added the following to my Bash profile:
alias libreconv='function _libreconv(){ /path/to/soffice --headless --convert-to "$1" "$2"; }; _libreconv'
However, when I now try to invoke the following:
libreconv odt *doc
this results in only the first doc file in the directory being converted, after which the the function exits and returns me to prompt... Maybe I am missing something obvious (I am a cli newb after all), but I do not understand why invoking the function results in only the first file being converted versus all files when I run the soffice command directly.
Thanks in advance for any aid helping me understand what is going wrong here. :)

Because your function only accepts two parameters.
Probably don't hardcode the path to soffice; instead, make sure your PATH includes the directory where it's installed.
The alias is completely useless here anyway; see also Why would I create an alias which creates a function?
If you wanted to create a function, try something like
libreconv () { soffice --headless --convert-to "$#"; }
The arguments "$1" and "$2" literally expand to the first two arguments. The argument "$#" expands to all the arguments, with quoting preserved (this is important if you want to handle file names with spaces in them etc; you see many scripts which incorrectly use "$*" or $# without the quotes).
Tangentially, if soffice is in a weird place which you don't want in your PATH, add a symlink to it in a directory which is in your PATH. A common arrangement is to have ~/bin and populate it with symlinks to odd binaries, including perhaps scripts of your own which are installed for development in a Git working directory somewhere.
A common incantation to have in your .bash_profile or similar is
if [[ -d ~/bin ]]; then
case :$PATH: in
*:~/bin:* | *:$HOME/bin:* ) ;;
*) PATH=~/bin:$PATH;;
esac
fi
With that, you can (create ~/bin if it doesn't exist; mkdir ~/bin) and ln -s /path/to/soffice ~/bin to create a symlink to the real location.

Related

Shell Command For File To Delete Itself

I want to make a file that runs a script, then deletes itself. I know that its root would most likely be "~/Library/Downloads/filename.app". How would I go about having it self destruct? I'm working in script editor.
I'm not sure if I understand correctly as shell script would traditionally have .sh suffix instead of .app one (if any) and I'm not familiar with anything that I'd call "script editor", but alas here's my solution.
If you are in bash environment, you can make use of the BASH_SOURCE array. Provided that you didn't change the current working directory, you can directly call
rm "${BASH_SOURCE[0]}"
(or just rm "$BASH_SOURCE").
If you are using cd or make larger script, it might be advisable to save fully resolved path to the script at the beginning and remove that file at the end (not somewhere in the middle as running bash scripts are NOT independent on their source files*) like so:
#!/bin/bash
self=$(realpath "${BASH_SOURCE[0]}")
#
# code so ugly I want to delete it when I'm done
#
rm "$self"
*Edit shell script while it's running

Run scripts without typing the extension

I use git-bash on windows.
Is there a way to make it run windows batches (.cmd) without typing file extensions?
And the same question about *.sh files. I prefer putting bash scripts into .sh to distinguish them in the explorer easier.
PS. Aliases or proxy-scripts without extension are not welcomed.
Hopefully, there is some geek's way to do this. So, still looking for an answer...
You could any flavour of bash (cmder, git-bash, ms bash, cygwin, gnu-utilities…) which will let you use any unix command without extension, even if stored as a .exe.
Take attention to the CR/LF, and use dos2unix at each shell script creation to prevent misinterpretation of line ending.
You won't be able to use .sh file without extension… but to create an alias for each script :
$ alias myscript="/usr/local/bin/myscript.sh"
Put them in your ~/.bashrc
First of all a bit of content about file extension:
A filename extension is an identifier specified as a suffix to the
name of a computer file. The extension indicates a characteristic of
the file contents or its intended use. A file extension is typically
delimited from the filename with a full stop (period), but in some
systems it is separated with spaces.
Some file systems implement filename extensions as a feature of the
file system itself and may limit the length and format of the
extension, while others treat filename extensions as part of the
filename without special distinction.
Filesystems for UNIX-like operating systems (opposed to DOS/Windows) do not separate the
extension metadata from the rest of the file name. The dot character
is just another character in the main filename, and filenames can have
multiple extensions, usually representing nested transformations, ...
Regarding your shell scripts:
So basically in Unix file extension are not important/not mandatory so you can directly omit them. If for any reason you want to keep them (and I believe that you should) then you can define an alias to them. (refer to https://askubuntu.com/questions/17536/how-do-i-create-a-permanent-bash-alias)
You must also keep in mind the EOL char ('\n' vs '\r\n' that differ between Unix and Windows.
Regarding your windows batches, you can not run them directly in a Unix like environment so you will not be able to run them at the same time from your git bash except if you use a tool like GH4W (github generate ssh key on windows) or use git-cmd.bat (What is the exact meaning of Git Bash?)
Maybe I've found a solution that requires some additional coding. Instead of insulting the user when typing a non-existent command, you could try to rewrite the code so it executes a command by adding '.sh' or '.cmd'.
GitGub: insult-linux-unix-bash-user-when-typing-wrong-command
Also do a search for the command_not_found_handle. This is available on most Linux systems and might or might not be available for git-bash.
This is something I was looking for myself and following on from FithAxiom's answer, have found a solution. It feels ugly, because it basically means overriding the "command not found" handling, and searching for the file ourselves. But it does achieve the desired effect.
This is a modification of an answer given in another thread. You add this to your .bashrc file. You can modify it to suit your needs (and improvements are also welcome).
command_not_found_handle()
{
cmd=$1
shift
args=( "$#" )
IFS=:
for dir in $PATH; do
if [ -f $dir/$cmd.cmd ]; then { "$dir/$cmd.cmd" "${args[#]}"; return; }
elif [ -f $dir/$cmd.ps1 ]; then { powershell.exe -file "$dir/$cmd.ps1" "${args[#]}"; return; }
elif [ -f $dir/$cmd.sh ]; then { "$dir/$cmd.sh" "${args[#]}"; return; }
fi
done
# Replicate standard "command not found" error
echo "bash: $1: command not found" >&2
return 127
}
You can create a file ~/.bashrc and override the command not found handler. Bash let you handle it. Read it for more info.
In .bashrc file you can update the command_not_found_handle function provided by bash.
command_not_found_handle() {
unset -f command_not_found_handle
command="$1".sh
shift
exec "$command" "$#"
}

Parent directory of a script

So I am a rookie in Linux and I need some help. I have to write a bash script in which I have to use the parent directory of the script to create a file there, wherever the script would be. It should look like this:
If my script it's in "/home/student/", I need to create, using an in-script command another file called txt in /home/. Any ideas please? Thank you.
There's a subtlety if you want to be able to run your script from anywhere.
eg: if your script is in /home/myHome/someDir/someOther, and you want to create a file in /home/myHome/someDir wherever you are when you run your script.
To solve it, you just need to first derive the directory where your script is.
It can be done using:
SCRIPT_DIRECTORY="$(dirname "$0")"
touch "$SCRIPT_DIRECTORY/../myFile.txt"
Edit: Actually it can be even more subtle, if you want to handle symlinks. ie: if the symlink /home/myHome/mySymlink points at your script, and is the one actually being called, then the previous script will consider /home/myHome/ instead of /home/myHome/someDir/someOther
To handle this case you can do
if [ -L "$0" ] && [ -x $(which readlink) ]; then
ACTUAL_SCRIPT_FILE="$(readlink -mn "$0")"
else
ACTUAL_SCRIPT_FILE="$0"
fi
SCRIPT_DIRECTORY="$(dirname "$ACTUAL_SCRIPT_FILE")"
touch "$SCRIPT_DIRECTORY/../myFile.txt"
use .. to point to parent directory. So you could create a file using something like
MY_SCRIPTDIR="$(dirname $0)"
touch ${MY_SCRIPTDIR}/../abc.txt
From your command prompt or within shell script.
Unfortunately, the other answers either give you the current working directory instead of the directory the script is in, or they will not work if either the script or one of the directories along the way is a symbolic link rather than a real directory.
What will work is:
dirname $(readlink -f "$0")
Explanation:
"$0" is the name of the script as you type it in your command line. Quoting is important for the case it contains whitespace.
readlink will resolve any symbolic links along the way
dirname takes just the directory name from script's full path - it's better readable and safer for corner cases than manually looking for slashes etc.
Now, you will get the correct result even in a complex case: if your script is in /tmp and you create a symbolic link to it in /tmp/abc/, and your current directory will be /home and you run /tmp/abc/your-script, it will correctly output /tmp, not /home nor /tmp/abc.

Why does this script work in the current directory but fail when placed in the path?

I wish to replace my failing memory with a very small shell script.
#!/bin/sh
if ! [ –a $1.sav ]; then
mv $1 $1.sav
cp $1.sav $1
fi
nano $1
is intended to save the original version of a script. If the original has been preserved before, it skips the move-and-copy-back (and I use move-and-copy-back to preserve the original timestamp).
This works as intended if, after I make it executable with chmod I launch it from within the directory where I am editing, e.g. with
./safe.sh filename
However, when I move it into /usr/bin and then I try to run it in a different directory (without the leading ./) it fails with:
*-bash: /usr/bin/safe.sh: /bin/sh: bad interpreter: Text file busy*
My question is, when I move this script into the path (verified by echo $PATH) why does it then fail?
D'oh? Inquiring minds want to know how to make this work.
The . command is not normally used to run standalone scripts, and that seems to be what is confusing you. . is more typically used interactively to add new bindings to your environment (e.g. defining shell functions). It is also used to similar effect within scripts (e.g. to load a script "library").
Once you mark the script executable (per the comments on your question), you should be able to run it equally well from the current directory (e.g. ./safe.sh filename) or from wherever it is in the path (e.g. safe.sh filename).
You may want to remove .sh from the name, to fit with the usual conventions of command names.
BTW: I note that you mistakenly capitalize If in the script.
The error bad interpreter: Text file busy occurs if the script is open for write (see this SE question and this SF question). Make sure you don't have it open (e.g. in a editor) when attempting to run it.

Deleting a directory contents using shell scripts

I am a newbie to Shell scripting. I want to delete all the contents of a directory which is in HOME directory of the user and deleting some files which are matching with my conditions. After googled for some time, i have created the following script.
#!/bin/bash
#!/sbin/fuser
PATH="$HOME/di"
echo "$PATH";
if [ -d $PATH ]
then
rm -r $PATH/*
fuser -kavf $PATH/.n*
rm -rf $PATH/.store
echo 'File deleted successfully :)'
fi
If I run the script, i am getting error as follows,
/users/dinesh/di
dinesh: line 11: rm: command not found
dinesh: line 12: fuser: command not found
dinesh: line 13: rm: command not found
File deleted successfully :)
Can anybody help me with this?
Thanks in advance.
You are modifying PATH variable, which is used by the OS defines the path to find the utilities (so that you can invoke it without having to type the full path to the binary). The system cannot find rm and fuser in the folders currently specified by PATH (since you overwritten it with the directory to be deleted), so it prints the error.
tl;dr DO NOT use PATH as your own variable name.
PATH is a special variable that controls where the system looks for command executables (like rm, fuser, etc). When you set it to /users/dinesh/di, it then looks there for all subsequent commands, and (of course) can't find them. Solution: use a different variable name. Actually, I'd recommend using lowercase variables in shell scripts -- there are a number of uppercase reserved variable names, and if you try to use any of them you're going to have trouble. Sticking to lowercase is an easy way to avoid this.
BTW, in general it's best to enclose variables in double-quotes whenever you use them, to avoid trouble with some parsing the shell does after replacing them. For example, use [ -d "$path" ] instead of [ -d $path ]. $path/* is a bit more complicated, since the * won't work inside quotes. Solution: rm -r "$path"/*.
Random other notes: the #!/sbin/fuser line isn't doing anything. Only the first line of the script can act as a shebang. Also, don't bother putting ; at the end of lines in shell scripts.
#!/bin/bash
path="$HOME/di"
echo "$path"
if [ -d "$path" ]
then
rm -r "$path"/*
fuser -kavf "$path"/.n*
rm -rf "$path/.store"
echo 'File deleted successfully :)'
fi
This line:
PATH="$HOME/di"
removes all the standard directories from your PATH (so commands such as rm that are normally found in /bin or /usr/bin are 'missing'). You should write:
PATH="$HOME/di:$PATH"
This keeps what was already in $PATH, but puts $HOME/di ahead of that. It means that if you have a custom command in that directory, it will be invoked instead of the standard one in /usr/bin or wherever.
If your intention is to remove the directory $HOME/di, then you should not be using $PATH as your variable. You could use $path; variable names are case sensitive. Or you could use $dir or any of a myriad other names. You do need to be aware of the key environment variables and avoid clobbering or misusing them. Of the key environment variables, $PATH is one of the most key ($HOME is another; actually, after those two, most of the rest are relatively less important). Conventionally, upper case names are reserved for environment variables; use lower case names for local variables in a script.

Resources