Bash command as variable - bash

I am trying to store the start of a sed command inside a variable like this:
sedcmd="sed -i '' "
Later I then execute a command like so:
$sedcmd s/$orig_pkg/$package_name/g $f
And it doesn't work. Running the script with bash -x, I can see that it is being expanded like:
sed -i ''\'''\'''
What is the correct way to express this?

Define a shell function:
mysed () {
sed -i "" "$#"
}
and call it like this:
$ mysed s/$orig_pkg/$package_name/g $f

It works when the command is only one word long:
$ LS=ls
$ $LS
But in your case, the shell is trying the execute the program sed -i '', which does not exist.
The workaround is to use $SHELL -c:
$ $SHELL -c "$LS"
total 0
(Instead of $SHELL, you could also say bash, but that's not entirely reliable when there are multiple Bash installations, the shell isn't actually Bash, etc.)
However, in most cases, I'd actually use a shell function:
sedcmd () {
sed -i '' "$#"
}

Why not use an alias or a function? You can do alias as
alias sedcmd="sed -i '' "

Not exactly sure what you're trying to do, but my suggestion is:
sedcmd="sed -i "
$sedcmd s/$orig_pkg/$package_name/g $f
You must set variables orig_pkg package_name and f in your shell first.
If you're replacing variable names in a file, try:
$sedcmd s/\$orig_pkg/\$package_name/g $f
Still f must be set to the file name you're working on.

This is the right way for do that
alias sedcmd="sed -i ''"
Obviously remember that when you close your bash, this alias will be gone.
If you want to make it "permanent", you have to add it to your .bashrc home file (if you want to make this only for a single user) or .bashrc global file, if you want to make it available for all users

Related

Optionally pass an argument in a bash script

I would like to use custom identity file when using rsync, but only if the file exists, otherwise I don't want to bother with custom ssh command for rsync. I am having problems with quotes. See examples.
Desired command if identity file exists
rsync -e "ssh -i '/tmp/id_rsa'" /tmp/dir/ u#h:/tmp/dir
Desired command if identity file does not exist
rsync /tmp/dir/ u#h:/tmp/dir
I wanted to create a variable that would contain -e "ssh -i '/tmp/id_rsa'" and use it as follows
rsync ${identityArg} /tmp/dir/ u#h:/tmp/dir
This variable would be either empty or contain desired ssh command.
An example way I fill the variable (I have tried many ways)
IDENTITY_FILE="/tmp/id_rsa"
if [ -f "${IDENTITY_FILE}" ]; then
identityArg="-e 'ssh -i \"${IDENTITY_FILE}\"'"
fi
The problem is that quotes are always wrong in the command and I end up with commands similar to these ones (set -x is set in the script and this is the output)
rsync -e '\ssh' -i '"/tmp/id_rsa"'\''' /tmp/dir/ u#h:/tmp/dir
There is something I do not get about quotation in bash. If you have any good resource about usage of single and double quotes in bash script I would like to read it.
You want to add two positional parameters: -e and ssh -i '/tmp/id_rsa', where /tmp/id_rsa is an expanded variable. You should use an array for this:
args=(/tmp/dir/ u#h:/tmp/dir)
idfile=/tmp/id_rsa
# Let [[ ... ]] do the quoting
if [[ -f $idfile ]]; then
# Prepend two parameters to args array
args=(-e "ssh -i '$idfile'" "${args[#]}")
fi
rsync "${args[#]}"
I'm not convinced the inner single quotes are necessary for ssh -i, but this expands to exactly the commands shown in the question.
Try like this
id=/tmp/id_rsa
[[ -e $id ]] && o1='-e' o2="ssh -i '$id'"
echo $o1 "$o2" /tmp/dir/ u#h:/tmp/dir
Trying to properly escape quotes is tricky. Better to try to leverage existing constructs. Few alternatives, depending on the situations
If the name of the identity file only contain simple characters (no spaces, wildcard, etc.) consider not wrapping it in quotes. In this case, you can
IDENTITY_FILE="/tmp/id_rsa"
if [ -f "${IDENTITY_FILE}" ]; then
identityArg="-e 'ssh -i ${IDENTITY_FILE}'"
fi
...
rsync $identityArg ...
Another option is to always pass in the command (ssh or 'ssh -I ...'). This will automatically take care for special characters in the identity file.
IDENTITY_FILE="/tmp/id_rsa"
if [ -f "${IDENTITY_FILE}" ]; then
identityArg="-i '${IDENTITY_FILE}'"
fi
rsync -e "ssh $identityArg" ...
Third alternative is to use array to create the arguments to rsync, and let the shell escape the characters as needed. This will allow any character in the identity file.
IDENTITY_FILE="/tmp/id_rsa"
if [ -f "${IDENTITY_FILE}" ]; then
identityArg=(-e "ssh -i '${IDENTITY_FILE}'")
fi
rsync "${identityArg[#]}" ...

Bash pass date variable as string

I have a script which creates another script when run like this:
cat > "$installpath""tweets.sh" << ENDOFFILE
#!/bin/bash
source "$installpath"config.sh
cd \$webdir
/usr/local/bin/twint -s "\$search" --limit \$scrapelimit -o \$csvname --csv --database \$dbfile -ho
FILE=\$csvname
NAME=\${FILE%.*}
EXT=\${FILE#*.}
DATE=`\date +%d-%m-%Y-%H-%M`
NEWFILE=\${NAME}_\${DATE}.\${EXT}
echo \$NEWFILE
mv \$csvname \$NEWFILE
export NEWFILE
export DATE
ENDOFFILE
However, the script interprets the
DATE=`\date +%d-%m-%Y-%H-%M`
and changes it to
DATE=28-09-2019-15-49
I have tried escaping the variables in every possible way but nothing seems to work. Any ideas?
I suggest to use:
DATE=\$(date +%d-%m-%Y-%H-%M)

How to translate an alias into a real file?

Most of the time, an alias works well, but some times, the command is executed by other programs, and they find it in the PATH, in this situation an alias not works as well as a real file.
e.g.
I have the following alias:
alias ghc='stack exec -- ghc'
And I want to translate it into an executable file, so that the programs which depending on it will find it correctly. And the file will works just like the alias does, including how it process it's arguments.
So, is there any tool or scripts can help doing this?
Here is my solution, I created a file named ghc as following:
#!/bin/sh
stack exec -- ghc "$#"
The reason why there is double quote around $# is explained here: Propagate all arguments in a bash shell script
So, is there any tool or scripts can help doing this?
A lazy question for a simple problem... Here's a function:
alias2script() {
if type "$1" | grep -q '^'"$1"' is aliased to ' ; then
alias |
{ sed -n "s#.* ${1}='\(.*\)'\$##\!/bin/sh\n\1 \"\${\#}\"#p" \
> "$1".sh
chmod +x "$1".sh
echo "Alias '$1' hereby scriptified. To run type: './$1.sh'" ;}
fi; }
Let's try it on the common bash alias ll:
alias2script ll
Output:
Alias 'll' hereby scriptified. To run type: './ll.sh'
What's inside ll.sh:
cat ll.sh
Output:
#!/bin/sh
ls -alF "${#}"

With Bash or ZSH is there a way to use a wildcard to execute the same command for each script?

I have a directory with script files, say:
scripts/
foo.sh
script1.sh
test.sh
... etc
and would like to execute each script like:
$ ./scripts/foo.sh start
$ ./scripts/script1.sh start
etc
without needing to know all the script filenames.
Is there a way to append start to them and execute? I've tried tab-completion as it's pretty good in ZSH, using ./scripts/*[TAB] start with no luck, but I would imagine there's another way to do so, so it outputs:
$ ./scripts/foo.sh start ./scripts/script1.sh start
Or perhaps some other way to make it easier? I'd like to do so in the Terminal without an alias or function if possible, as these scripts are on a box I SSH to and shouldn't be modifying *._profile or .*rc files.
Use a simple loop:
for script in scripts/*.sh; do
"$script" start
done
There's just one caveat: if there are no such *.sh files, you will get an error. A simple workaround for that is to check if $script is actually a file (and executable):
for script in scripts/*.sh; do
[ -x "$script" ] && "$script" start
done
Note that this can be written on a single line, if that's what you're after for:
for script in scripts/*.sh; do [ -x "$script" ] && "$script" start; done
Zsh has some shorthand loops that bash doesn't:
for f (scripts/*.sh) "$f" start

How to get name of alias that invoked bash script

$0 expands to the name of the shell script.
$ cat ./sample-script
#!/bin/bash
echo $0
$ chmod 700 ./sample-script
$ ./sample-script
./sample-script
If the shell script is invoked via a symbolic link, $0 expands to its name:
$ ln -s ./sample-script symlinked-script
$ ./symlinked-script
./symlinked-script
How could I get the name of an alias? Here `$0' expands again to the filename:
$ alias aliased-script=./sample-script
$ aliased-script
./sample-script
Aliases are pretty dumb, according to the man page
...Aliases are expanded when a command is read, not when it is executed...
so since bash is basically just replacing a string with another string and then executing it, there's no way for the command to know what was expanded in the alias.
I imagine you already know this, but for the record the answer is: you need cooperation by the code implementing the alias.
alternate_name () {
MY_ALIAS_WAS=alternate_name real_name "$#"
}
or, if you really want to use the superseded alias syntax:
alias alternate_name="MY_ALIAS_WAS=alternate_name real_name"
...and then...
$ cat ~/bin/real_name
#!/bin/sh
echo $0, I was $MY_ALIAS_WAS, "$#"
bash does not make this available. This is why symlinks are used to invoke multiplex commands, and not aliases.

Resources