How to source bash script in zsh - bash

I have bash script which heavily uses bash features and does not run in zsh, dash, and sh.
My prefered shell is zsh. How can I source this bash script into zsh shell ?
I tried
function in() {
bash -c "'$# ; exec zsh'"
}
but this gives me syntax errors like this
❯ in source t.bash
t.bash ; exec zsh': -c: line 1: unexpected EOF while looking for matching `''
t.bash ; exec zsh': -c: line 2: syntax error: unexpected end of file
I also tried to use xargs but then enviroment variables set by my bash script are not set in zsh
function in () {
echo -c "'$# ; exec zsh'" | xargs -t bash
}
but then enviroment variables set by my bash script are not set in zsh
copy pasting the command shown by xargs -t does work

You said "copy pasting the command shown by xargs -t does work".
So ... if you changed
function in () {
echo -c "'$# ; exec zsh'" | xargs -t bash
}
to
function in () {
echo -c "'$# ; exec zsh'" | xargs -t bash >"${BATCH_FILE}"
chmod 754 "${BATCH_FILE}" ; "${BATCH_FILE}"
}
wouldn't that give you what you need?

Related

Why can't pass the variable's value into file in /etc directory?

I want to pass the value of the $ip variable into the file /etc/test.json with bash.
ip="xxxx"
sudo bash -c 'cat > /etc/test.json <<EOF
{
"server":"$ip",
}
EOF'
I expect the content of /etc/test.json to be
{
"server":"xxxx",
}
However the real content in /etc/test.json is:
{
"server":"",
}
But if I replace the target directory /etc/ with /tmp
ip="xxxx"
cat > /tmp/test.json <<EOF
{
"server":"$ip",
}
EOF
the value of the $ip variable gets passed into /tmp/test.json:
$ cat /tmp/test.json
{
"server":"xxxx",
}
In Kamil Cuk's example, the subprocess is cat > /etc/test.json which contains no variable.
sudo sh -c 'cat > /etc/test.json' << EOF
{
"server":"$ip",
}
EOF
It does not export the $ip variable at all.
Now let's make an analysis for the following:
ip="xxxx"
sudo bash -c "cat > /etc/test.json <<EOF
{
"server":\""$ip"\",
}
EOF"
The different parts in
"cat > /etc/test.json <<EOF
{
"server":\""$ip"\",
}
EOF"
will concatenate into a long string and as a command .Why can the $ip variable inherit the value from its father process here?
There are two reasons for this behavior:
Per default, variables are no passed to the environment of subsequently executed commands.
The variable is not expanded in the current context, because your command is wrapped in single quotes.
Exporting the variable
Place an export statement before the variable, see man 1 bash
The supplied names are marked for automatic export to the environment of subsequently executed commands.
And as noted by Léa Gris you also need to tell sudo to preserve the environment with the -E or --preserve-environment flag.
export ip="xxxx"
sudo -E bash -c 'cat > /etc/test.json <<EOF
{
"server":"$ip",
}
EOF'
Expand the variable in the current context:
This is the reason your second command works, you do not have any quotes around the here document in this example.
But if I replace the target directory /etc/ with /tmp [...] the value of the $ip variable gets passed into /tmp/test.json
You can change your original snippet by replacing the single quotes with double quotes and escaping the quotes around your ip:
ip="xxxx"
sudo bash -c "cat > /etc/test.json <<EOF
{
"server":\""$ip"\",
}
EOF"
Edit: For your additional questions:
In Kamil Cuk's example, the subprocess is cat > /etc/test.json which contains no variable.
sudo sh -c 'cat > /etc/test.json' << EOF
{
"server":"$ip",
}
EOF
It does not export the $ip variable at all.
Correct and you did not wrap the here document in single quotes. Therefore $ip is substituted in the current context and the string passed to subprocesses standard input is
{
"server":"xxxx",
}
So in this example the subprocess does not need to know the $ip variable.
Simple example
$ x=1
$ sudo -E sh -c 'echo $x'
[sudo] Password for kalehmann:
This echos nothing because
'echo $x' is wrapped in single quotes. $x is therefore not substituted in the current context
$x is not exported. Therefore the subprocess does not know its value.
$ export y=2
$ sudo -E sh -c 'echo $y'
[sudo] Password for kalehmann:
2
This echos 2 because
'echo $y' is wrapped in single quotes. $x is therefore not substituted in the current context
$y is exported. Therefore the subprocess does know its value.
$ z=3
$ sudo -E sh -c "echo $z"
[sudo] Password for kalehmann:
3
This echos 3 because
"echo $z" is wrapped in double quotes. $z is therefore substituted in the current context
There little need to do the here document inside the subshell. Just do it outside.
sudo tee /etc/test.json <<EOF
{
"server":"$ip",
}
EOF
or
sudo sh -c 'cat > /etc/test.json' << EOF
{
"server":"$ip",
}
EOF
Generally, it is not safe to build a fragment of JSON using string interpolation, because it requires you to ensure the variables are properly encoded. Let a tool like jq to that for you.
Pass the output of jq to tee, and use sudo to run tee to ensure that the only thing you do as root is open the file with the correct permissions.
ip="xxxx"
jq --arg x "$ip" '{server: $x}' | sudo tee /etc/test.json > /dev/.null

Bash function argument parsing

I want to have a function I can call from the command line that takes the following:
$ command_name /some/path/file.java
and turns into the following call:
command /some/path:file
So basically the part I'm having trouble with is substituting a : for the last / and stripping the file extension.
It's not 100% clear what you question is. Do you want a bash function or a bash script? Splitting paths and files is easily done with the commands basename and dirname.
e.g.:
$ dirname /path/to/file.txt
/path/to
$ basename /path/to/file.txt
file.txt
But if you must do it with a regex, sed works well:
$ echo /path/to/file.txt | sed "s/.*\///"
file.txt
$ echo /path/to/file.txt | sed -r "s/(.+)\/.+/\1/"
/path/to
First a script:
#! /usr/bin/env bash
COMMAND="/bin/echo"
JAVA="$1"
path=`dirname "$JAVA"`
file=`basename "$JAVA"`
exec "$COMMAND" "$path:$file"
And now a function:
fnA()
{
COMMAND="$1"
JAVA="$2"
path=`dirname "$JAVA"`
file=`basename "$JAVA"`
exec "$COMMAND" "$path:$file"
}
Assuming that the original path string,
/some/path/file.java
is passed to your script as $1, you get the modified string as
path_string_with_colon=$(dirname $1):$(basename $1 .java)

How to set a bash variable in a compound xargs statement

I am looking for a way to set a variable in the statements passed to xargs. The value is to be manipulated in one of the commands. Using a file or another utility is an option but I am not sure why setting the bash variable in the sequence is always coming up as empty.
$ ls c*txt
codebase.txt consoleText.txt
$ ls c*txt | xargs -i bash -c "echo processing {}; v1={} && echo ${v1/txt/file}"
codebase.txt consoleText.txt
processing codebase.txt
processing consoleText.txt
The example above distills the question to the basics. I was expecting the behavior to be something like this but inline:
$ fname=codebase.txt; echo ${fname/txt/file}
codebase.file
Thank you.
This line is resolving ${v1/txt/file} to a value before the command is executed:
$ ls c*txt | xargs -i bash -c "echo processing {}; v1={} && echo ${v1/txt/file}"
And that means the bash -c doesn't even see ${v1/txt/file}
In this line the single quotes inhibit the variable substitution so echo processing {}; v1={} && echo ${v1/txt/file} is actually passed to bash -c as a parameter:
$ ls c*txt | xargs -i bash -c 'echo processing {}; v1={} && echo ${v1/txt/file}'
You could accomplish the same thing by escaping the dollar sign:
$ ls c*txt | xargs -i bash -c "echo processing {}; v1={} && echo \${v1/txt/file}"

Bash function - second parameter in a function not taken

For some reason I cannot pass the 2nd parameter to a function which is on a another file, exactly here:
$lsValidLocal | xargs -n 1 -I {} bash -c 'Push "{}" "**$inFolder**"
The Push function on functions.sh does not read the 2nd parameter $inFolder.
I tried several different ways, the only working way till now is exporting the variable to make it globally accessible (not a good solution though)
script.sh
#!/bin/bash
#other machine
export otherachine="IP_address_otherachine"
#folders
inFolder="$HOME/folderIn"
outFolder="$HOME/folderOut"
#loading functions.sh
. /home/ec2-user/functions.sh
export lsValidLocal="lsValid $inFolder"
echo $inFolder
#execution
$lsValidLocal | xargs -n 1 -I {} bash -c 'Push "{}" "$inFolder"'
functions.sh
function Push() {
local FILE=$1
local DEST=$2
scp $FILE $otherachine:$DEST &&
rm $FILE ${FILE}_0 &&
ssh $otherachine "touch ${FILE}_0"
}
function lsValid() { #from directory
local DIR=$1
ls $DIR/*_0 | sed 's/.\{2\}$//'
}
export -f Push
export -f Pull
export -f lsValid
The problem with the code you have written is that $inFolder is inside single quotes (') which will prevent it being expanded.
$lsValidLocal | xargs -n 1 -I {} bash -c 'Push "{}" "**$inFolder**"'
This will be executed as three separate layers of processes
bash <your scrpit>
|
\xargs ...
|
\bash -c Push ...
Your code is not transferring the value across from the outer shell to inner shell... But you are expanding the variable inFolder using the inner shell. As you correctly point out it can be done with an exported environment variable.
The alternative is to have the outer shell expand it before passing to xargs.
$lsValidLocal | xargs -n 1 -I {} bash -c "Push '{}' '**$inFolder**'"
Notice I have reversed ' and " to allow $inFolder to be expanded before xargs is called.

Pipe commands in bash function

When you define a bash function you can call bash commands with command command.
function ls() {
clear
command ls "$#"
}
How would you pipe commands in bash function?
e.g.
function ls() {
clear
command ls "$#" | head
}
EDIT: The output would be OK, but there is --color=auto. Look here
Try this in your ~/.bashrc
function ls() { clear ; builtin ls "$#" | head ; }
It's similar to the function you have already but with the inclusion of builtin, it guarantees not to get stuck in a loop calling itself. Hope this works!
EDIT: It should be noted that any colour information produced by ls with the --color=auto option won't be carried through the pipe to head.
You can pipe the colour information generated by the ls command to head if you run ls in a so-called pseudo terminal (so that ls thinks it is writing its output to a terminal, and not a pipe). This can be achieved by using the script command.
ls() {
type -P command 1>/dev/null ||
{ echo 'No "command" executable found!'; return 1; }
clear
script -q /dev/null command ls -G "$#" | tr -d '\r' | head
}
cat /usr/bin/command # on Mac OS X 10.6.8
#!/bin/sh
# $FreeBSD: src/usr.bin/alias/generic.sh,v 1.2 2005/10/24 22:32:19 cperciva Exp $
# This file is in the public domain.
builtin `echo ${0##*/} | tr \[:upper:] \[:lower:]` ${1+"$#"}
For more information see: ls command operating differently depending on recipient

Resources