Read response and if then else loop - bash

I have this piece of rudementary code in my .bash_profile that loads on login, but I can't get it working. Probably some easy fix, but I', staring my self blind on it right now.
The code:
# Simple backup when editing files with nano
function bu() {
read -p "Backup >>"`basename $1`"<< b4 edit [Y/n]?" response
echo $response
response=$response${response,,} # tolower
if [[ $response =~ ^(yes|y| ) ]]; then
mkdir -p ~/.backup
#cp -v "$1" ~/.backup/`basename $1`-`date +%Y%m%d%H%M`.backup
cp "$1" ~/.backup/`basename $1`-`date +%Y%m%d%H%M`.backup
echo ~/.backup/`basename $1`-`date +%Y%m%d%H%M`.backup >> ~/.backup/bu_log.txt
nano "$1"
else
nano "$1"
fi
}
And it has an alias nano="bu"
so, when i write nano, it should ask me if i want to backup the file first (on yes) or just open it in nano straight away.
The only thing that happens now is that it keeps asking the question and looping, newer goes to nano.
CentOS is the linux

Since nano is an alias to bu, typing nano runs your function, which calls nano, which is an alias to bu, which calls nano, ...
In order to run the actual nano editor, you need to disable alias expansion for that call. Use the command built-in:
command nano "$1"

You are calling nano recursivly, since you aliased nano=bu
so try to change the line in the script
nano "$1"
to the full path of
/usr/bin/nano "$1"
(or where nano is installed on your system)

I think you want:
response=${response,,}
You have
response=$response${response,,}
which gets you response=Yy. That won't match your regular expression.
You could also just do shopt -s nocasematch.
Aliases are usually trouble. The rule is "if in doubt, use a function."
nano() {
bu "$#"
}
bu() ( # Use a subshell to avoid having to reset shell options
shopt -s nocasematch
local base=${1##*/}
read -p "Backup >>${base}<< b4 edit [Y/n]?" response
case $response in
y*)
mkdir -p ~/.backup
local backup=~/.backup/"${base}-$(date +%Y%m%d%H%M`).backup"
cp "$1" "$backup"
echo "$backup" >> ~/.backup/bu_log.txt
;;
esac
command nano "$#" # Use "$#" to allow you to pass more than one argument to nano
)

Related

terminal title not setting within screen

Currently, I'm setting terminal title within screen command, but the bash script gives me:
Cannot exec 'source /etc/profile && title.set root#test': No such file or directory
And I can run above command successful directly from the command line, here are my scripts:
/usr/local/bin/s
#!/bin/bash
if [ $1 ]
then
screen -D -R $1 -m "source /etc/profile && title.set `whoami`#$1"
else
screen -R
fi
/etc/profile
...
# Source global bash config
if test "$PS1" && test "$BASH" && test -z ${POSIXLY_CORRECT+x} && test -r /etc/bash.bashrc; then
. /etc/bash.bashrc
fi
function title.set() {
if [[ -z "$ORIG" ]]; then
ORIG=$PS1
fi
TITLE="\[\e]2;$*\a\]"
PS1=${ORIG}${TITLE}
}
# Termcap is outdated, old, and crusty, kill it.
unset TERMCAP
# Man is much better than us at figuring this out
unset MANPATH
...
So What's going wrong here?
The keyword source is a bash built-in command, i.e., something for which there is not necessarily an actual file to exec (another built-in command). You can only exec something that is a file — like bash, e.g., something like this:
screen -D -R $1 -m bash -c "source /etc/profile && title.set `whoami`#$1"

Starting Midnight Commander `mc` with sudo alias and preserve path

Is it possible to start the mc-wrapper with sudo and still use the last selected directory on the console when quitting sudo mc (requirement number 4)? My default alias looks like this.
alias mc='EDITOR="${EDITOR-mcedit}" . /usr/lib/mc/mc-wrapper.sh'
Some errors (for the Googlers)
sudo: mc: command not found
sudo: .: command not found # "." == "source"
My requirements
Ubuntu 18.04.1.
The alias should work with and without sudo call.
If possible, a single alias for mc in /etc/bash.bashrc for all users.
The directory you changed to with sudo mc should be "preserved" after closing the Midnight Commander. This means that you will not be in the same directory as you started sudo mc (provided it is not the same directory).
Optional requirements
See if the alias was started with super powers.
See if the alias was started with sudo.
If the alias mc was started without super powers or sudo, ask if the program mc should still be started with sudo rights.
If the question is answered No, use my default alias.
In all other cases, the first four requirements should be met.
The editor (e.g. mcedit or vi) within mc should be selectable via another alias like mcvi (for vi) without code duplication.
Arguments should be passed on to the program, like sudo mc /opt/ /mnt/
Here's one hacky solution (tested, but the last two optional requirements are still missing).
/etc/bash.bashrc
alias sudo='sudo ' # fixes "sudo: mc: command not found" (breaks with an argument: sudo -H ll)
# sudo apt update && sudo apt install dialog mc pwgen
#
# Start the alias with a "real" command (instead of a shell keyword like "if") so that sudo is not confused.
# This first command (env) will eat sudo and all arguments! Even the following file redirection including the angle bracket is already executed without sudo.
alias mc='env > "/tmp/mc.env.$(whoami).$$"
MC_USER="$(whoami)"
MC_ENV_FILE="/tmp/mc.env.${MC_USER}.$$"
# cat "${MC_ENV_FILE}"
if [ "$(cat "${MC_ENV_FILE}" | grep "$(id -nu 0)" | wc -l)" -gt "3" ]; then
# echo "This alias was called with super powers."
MC_ROOT="root"
fi
if [ "$(cat "${MC_ENV_FILE}" | grep "^SUDO_" | wc -l)" -gt "3" ]; then
# echo "This alias was called with sudo (possibly sudo -i or sudo -s was entered before)."
MC_SUDO="sudo"
fi
if [ "${MC_ROOT}" == "root" ] || [ "${MC_SUDO}" == "sudo" ]; then
MC_DIALOG="0"
else
# echo "This alias was called as normal user."
dialog --cr-wrap --defaultno --title "sudo mc" --yesno "Do you want super powers (sudo/root)?\n\n(Alternatively you can use \"sudo mc\" directly next time.)\n\nAgain: Do you want super powers (sudo/root)?" 9 64
MC_DIALOG="$?"
tput reset
fi
if [ "${MC_DIALOG}" != "0" ]; then
# echo "No, do not use sudo and stay normal user."
# echo "Standard wrapper (without arguments)..."
EDITOR="${EDITOR-mcedit}" . /usr/lib/mc/mc-wrapper.sh # does not work with sudo because "." is not a program like "ls" or "grep"!
else
# echo "Yes, exec those decisive commands with super powers."
# echo "Custom wrapper (also without arguments)..."
MC_PWD_FILE_DIRNAME="${TMPDIR-/tmp}/mc-${MC_USER}/"
MC_PWD_FILE="${MC_PWD_FILE_DIRNAME}mc.pwd.$$.$(pwgen 13 1)"
sudo mkdir -p "$MC_PWD_FILE_DIRNAME"
sudo chown "$(sudo whoami)":"$(sudo whoami)" "$MC_PWD_FILE_DIRNAME"
sudo chmod 0700 "$MC_PWD_FILE_DIRNAME"
sudo EDITOR="${EDITOR-mcedit}" /usr/bin/mc -P "$MC_PWD_FILE"
sudo chown -R "$MC_USER":"$MC_USER" "$MC_PWD_FILE_DIRNAME"
if test -r "$MC_PWD_FILE"; then
MC_PWD=$(cat "$MC_PWD_FILE")
if test -n "$MC_PWD" && test -d "$MC_PWD"; then
cd "$MC_PWD"
fi
unset MC_PWD
fi
rm -f "$MC_PWD_FILE"
unset MC_PWD_FILE
unset MC_PWD_FILE_DIRNAME
fi
unset MC_DIALOG
unset MC_SUDO
unset MC_ROOT
rm -f "${MC_ENV_FILE}"
unset MC_ENV_FILE
unset MC_USER
# This terminating line break is required:
'
I didn't manage to use a function mcwrapper (and $(declare -f mcwrapper)) and I don't think it's that easy either!?

open file if it exists, otherwise, create a new one and open it

I currently have the following alias set up:
alias emacs='open -a Emacs'
But obviously, if the file doesn't exist already, it gives me an error. Is there a way to change this alias to basically say "if the file exists open it in Emacs, otherwise, create the file and then open it in Emacs?"
Thanks!
Use a function instead of an alias. Functions can do everything aliases can do and much more.
emacs() {
if [ ! -f "$1" ]; then
touch "$1"
fi
open -a Emacs "$1"
}
That should work for one file. For multiple files you can use this.
emacs() {
for file; do
if [ ! -f "$file" ]; then
touch "$file"
fi
done
open -a Emacs "$#"
}
You can consider an alias with --args option for open command like this:
alias emacs='open -a Emacs --args= '
Then call it as:
emacs $PWD/file.txt
As per man open:
--args
All remaining arguments are passed to the opened application in the argv parameter
to main(). These arguments are not opened or interpreted by the open tool.
Or create a small script called omacs with this:
#!/bin/bash
for f in $*; do if [ -f $f ]; then open -a Emacs $f; else touch $f; open -a Emacs $f; fi; done
And create this alias
alias emacs='omacs'
Works with multiple files.

Create shell sub commands by hierarchy

I'm trying to create a system for my scripts -
Each script will be located in a folder, which is the command itself.
The script itself will act as a sub-command.
For example, a script called "who" inside a directory called "git",
will allow me to run the script using git who in the command line.
Also, I would like to create a sub command to a psuedo-command, meaning a command not currently available. E.g. some-arbitrary-command sub-command.
Is that somehow possible?
I thought of somehow extending https://github.com/basecamp/sub to accomplish the task.
EDIT 1
#!/usr/bin/env bash
command=`basename $0`
subcommand="$1"
case "$subcommand" in
"" | "-h" | "--help" )
echo "$command: Some description here" >&2
;;
* )
subcommand_path="$(command -v "$command-$subcommand" || true)"
if [[ -x "$subcommand_path" ]]; then
shift
exec "$subcommand_path" "${#}"
return $?
else
echo "$command: no such command \`$subcommand'" >&2
exit 1
fi
;;
esac
This is currently the script I run for new custom-made commands.
Since it's so generic, I just copy-paste it.
I still wonder though -
can it be generic enough to just recognize the folder name and create the script by its folder name?
One issue though is that it doesn't seem to override the default command name, if it supposed to replace it (E.g. git).
EDIT 2
After tinkering around a bit this is what I came to eventuall:
#!/usr/bin/env bash
COMMAND=`basename $0`
SUBCOMMAND="$1"
COMMAND_DIR="$HOME/.zsh/scripts/$COMMAND"
case "$SUBCOMMAND" in
"" | "-h" | "--help" )
cat "$COMMAND_DIR/help.txt" 2>/dev/null ||
command $COMMAND "${#}"
;;
* )
SUBCOMMAND_path="$(command -v "$COMMAND-$SUBCOMMAND" || true)"
if [[ -x "$SUBCOMMAND_path" ]]; then
shift
exec "$SUBCOMMAND_path" "${#}"
else
command $COMMAND "${#}"
fi
;;
esac
This is a generic script called "helper-sub" I symlink to all the script directories I have (E.g. ln -s $HOME/bin/helper-sub $HOME/bin/ssh).
in my zshrc I created this to call all the scripts:
#!/usr/bin/env bash
PATH=${PATH}:$(find $HOME/.zsh/scripts -type d | tr '\n' ':' | sed 's/:$//')
export PATH
typeset -U path
for aliasPath in `find $HOME/.zsh/scripts -type d`; do
aliasName=`echo $aliasPath | awk -F/ '{print $NF}'`
alias ${aliasName}=${aliasPath}/${aliasName}
done
unset aliasPath
Examples can be seen here: https://github.com/iwfmp/zsh/tree/master/scripts
You can't make a directory executable as a script, but you can create a wrapper that calls the scripts in the directory.
You can do this either with a function (in your profile script or a file in your FPATH) or with a wrapper script.
A simple function might look like:
git() {
local subPath='/path/to/your/git'
local sub="${1}" ; shift
if [[ -x "${subPath}/${1}" ]]; then
"${subPath}/${sub}" "${#}"
return $?
else
printf '%s\n' "git: Unknown sub-command '${sub}'." >&2
return 1
fi
}
(This is the same way that the sub project you linked works, just simplified.)
Of course, if you actually want to create a sub-command for git specifically (and that wasn't just an example), you'll need to make sure that the built-in git commands still work. In that case you could do like this:
git() {
local subPath='/path/to/your/git'
local sub="${1}"
if [[ -x "${subPath}/${sub}" ]]; then
shift
"${subPath}/${sub}" "${#}"
return $?
else
command git "${#}"
return 1
fi
}
But it might be worth pointing out in that case that git supports adding arbitrary aliases via git config:
git config --global alias.who '!/path/to/your/git/who'

Bash script: only echo line to ~/.bash_profile once if the line doesn't yet exist

I wrote a bash git-install script. Toward the end, I do:
echo "Edit ~/.bash_profile to load ~/.git-completioin.bash on Terminal launch"
echo "source ~/.git-completion.bash" >> ~/.bash_profile
The problem is, if you run the script more than once, you end up appending this line multiple times to ~/.bash_profile. How do I use bash scripting with grep or sed (or another option you may recommend) to only add the line if it doesn't yet exist in the file. Also, I want to add the line to ~/.profile if that file exists and ~/.bash_profile doesn't exist, otherwise just add it to ~/.bash_profile.
Something like this should do it:
LINE_TO_ADD=". ~/.git-completion.bash"
check_if_line_exists()
{
# grep wont care if one or both files dont exist.
grep -qsFx "$LINE_TO_ADD" ~/.profile ~/.bash_profile
}
add_line_to_profile()
{
profile=~/.profile
[ -w "$profile" ] || profile=~/.bash_profile
printf "%s\n" "$LINE_TO_ADD" >> "$profile"
}
check_if_line_exists || add_line_to_profile
A couple of notes:
I've used the . command instead of source as source is a bashism, but .profile may be used by non-bash shells. The command source ... is an error in .profile
I've used printf instead of echo because it's more portable and wont screw up backslash-escaped characters as bash's echo would.
Try to be a little more robust to non-obvious failures. In this case make sure .profile exists and is writable before trying to write to it.
I use grep -Fx to search for the string. -F means fixed strings, so no special characters in the search string needs to be escaped, and -x means match the whole line only. The -qs is common grep syntax for just checking the existence of a string and not to show it.
This is proof of concept. I didn't actually run this. My bad, but it's Sunday morning and I want to go out and play.
if [[ ! -s "$HOME/.bash_profile" && -s "$HOME/.profile" ]] ; then
profile_file="$HOME/.profile"
else
profile_file="$HOME/.bash_profile"
fi
if ! grep -q 'git-completion.bash' "${profile_file}" ; then
echo "Editing ${profile_file} to load ~/.git-completioin.bash on Terminal launch"
echo "source \"$HOME/.git-completion.bash\"" >> "${profile_file}"
fi
How about:
grep -q '^source ~/\.git-completion\.bash$' ~/.bash_profile || echo "source ~/.git-completion.bash" >> ~/.bash_profile
or in a more explicit (and readable) form:
if ! grep -q '^source ~/\.git-completion\.bash$' ~/.bash_profile; then
echo "Updating" ~/.bash_profile
echo "source ~/.git-completion.bash" >> ~/.bash_profile
fi
EDIT:
You should probably add an additional newline before your one-liner, just in case ~/.bash_profile does not end in one:
if ! grep -q '^source ~/\.git-completion\.bash$' ~/.bash_profile; then
echo "Updating" ~/.bash_profile
echo >> ~/.bash_profile
echo "source ~/.git-completion.bash" >> ~/.bash_profile
fi
EDIT 2:
This is a bit easier to modify and slightly more portable:
LINE='source ~/.git-completion.bash'
if ! grep -Fx "$LINE" ~/.bash_profile >/dev/null 2>/dev/null; then
echo "Updating" ~/.bash_profile
echo >> ~/.bash_profile
echo "$LINE" >> ~/.bash_profile
fi
The -F and -x options are specified by POSIX and were suggested in several other answers and comments.
# Decide which profile to add to
PROFILE=~/.bash_profile
if ! [ -e "$PROFILE" ] && [ -e ~/.profile ]; then
PROFILE=~/.profile
fi
# Add to profile if it doesn't appear to be there already. Err on the side of
# not adding it, in case user has made edits to their profile.
if ! grep -s 'git-completion\.bash' "$PROFILE"; then
echo "Editing $PROFILE to load ~/.git-completion.bash on Terminal launch"
echo "source ~/.git-completion.bash" >> "$PROFILE"
fi

Resources