Create a heredoc that doesn't interpret anything - linux

I'm writing a script for a friend who is not experienced with bash. The script generates a backup script, generates a crontab and runs crontab to create a cron job.
I want to date these backups, so currently the script (what's relevant) is:
cat <<EOF > ~/scheduledBackups/scripts/finalCutScript.bash
mkdir -p ~/scheduledBackups/FinalCut-`date +%a-%b-%d-%Y_%H`
cp -r $BACKUP_DIR/* ~/scheduledBackups/FinalCut-`date +%a-%b-%d-%Y_%H`
EOF
This, however, generates finalCutScript.bash with the date as is when the "installer" script is run.
Is there a way to place exactly that heredoc within finalCutScript.bash? I want to keep everything in one script so that I can use this script framework later.
Elaboration
Expected behaviour:
I want the file that the heredoc is piped into to contain
mkdir -p ~/scheduledBackups/FinalCut-`date +%a-%b-%d-%Y_%H`
cp -r $BACKUP_DIR/* ~/scheduledBackups/FinalCut-`date +%a-%b-%d-%Y_%H`
Actual behaviour
The file generated by that heredoc contains
mkdir -p ~/scheduledBackups/FinalCut-Fri-Aug-05-2016_16
cp -r ~/Documents//* ~/scheduledBackups/FinalCut-Fri-Aug-05-2016_16

You should EOF in heredoc and use $(...) for command substitution:
cat <<-'EOF' >~/scheduledBackups/scripts/finalCutScript.bash
mkdir -p ~/scheduledBackups/FinalCut-$(date +%a-%b-%d-%Y_%H)
cp -r $BACKUP_DIR/* ~/scheduledBackups/FinalCut-$(date +%a-%b-%d-%Y_%H)
EOF
Update: As per OP's comment below you can also escape $ for not expanding a variable in current shell:
BACKUP_DIR='foobar' # variable to be used below in here-doc
cat <<-EOF >~/scheduledBackups/scripts/finalCutScript.bash
mkdir -p ~/scheduledBackups/FinalCut-\$(date +%a-%b-%d-%Y_%H)
cp -r $BACKUP_DIR/* ~/scheduledBackups/FinalCut-\$(date +%a-%b-%d-%Y_%H)
EOF
Above command will use $BACKUP_DIR from your current environment but will add literal $(date +%a-%b-%d-%Y_%H) in the output.

Related

Bash script tee command syntax issue

I want to echo the following line at the end of ~/.profile file using tee command:
export PATH="$HOME/.local/bin:$PATH"
To do this my bash script looks like this
#!/bin/bash
path_env="export PATH="$HOME/.local/bin:$PATH""
echo $path_env| sudo tee -a $HOME/.profile > /dev/null
But whenever I am executing the script it is also executing $PATH and $HOME value and inserts that in ~./profile file which I do not want. I only want the exact line to be passed by the bash script instead of replacing $PATH and $HOME with its own values.
I only want the exact line to be passed by the bash script instead of replacing $PATH and $HOME with its own values.
Och, right, so do not expand it. Quoting.
path_env='export PATH="$HOME/.local/bin:$PATH"'
echo "$path_env" | sudo tee -a "$HOME/.profile" > /dev/null

Need help using the pipe command in terminal (Linux / shell file)

Doing an assignment for class that needs to be done using commands in the terminal. I have a shell file (temp1.sh) created in the home directory, and a shell file (temp2.sh) created in a folder (randomFolder). When I run temp2.sh I need to display the amount of characters in temp1.sh. I need to use the pipe command to accomplish this.
So I figure I need to change directory to the home directory then open the file temp1.sh and use thewc -c command to display the characters. I have been trying many different ways to execute this task and somehow can't get it to work. Any help would be appreciated. Without using a pipe I can get it to work, but I can't seem to write out this command line properly while using a pipe.
What I have done so far:
cd ~
touch temp1.sh
chmod 755 temp1.sh
echo 'This file has other commands that are not relevant and work' >> temp1.sh
mkdir randomFolder
cd randomFolder
touch temp2.sh
chmod 755 temp2.sh
echo cd ~ | wc -c temp1.sh >> temp2.sh
This last line tells me there is no such file "temp1.sh" after I run it. if I redirect to home then type wc -c temp1.sh, I get the desired output. I want this output to happen when I run temp2.sh.
Example without using pipe command:
echo wc -c ~/temp1.sh >> temp2.sh
This gives me the desired output when I run temp2.sh. However I need to accomplish this while using the pipe command.
Your code is close to working. The first part is fine:
cd ~
touch temp1.sh
chmod 755 temp1.sh
echo 'This file has other commands that are not relevant and work' >> temp1.sh
mkdir randomFolder
cd randomFolder
touch temp2.sh
chmod 755 temp2.sh
All of that should work. You problem is this part:
echo cd ~ | wc -c temp1.sh >> temp2.sh
You need to separate the cd ~ from something that runs some command and pipes the output to wc, and get the whole lot stored in temp2.sh. That could be something like:
echo "cd $HOME" > temp2.sh
echo "cat temp1.sh | wc -c" >> temp2.sh
The key point here is using separate lines for the cd command and the wc command. Using > for the first command ensures that you don't have stray garbage from previous failed attempts in temp2.sh. You can achieve the same result in multiple ways, including:
echo "cd; cat temp1.sh | wc -c" > temp2.sh
echo "cd ~; while read -r line; do echo "$line"; done < temp1.sh | wc -c" > temp2.sh
And then, finally, you need to execute temp2.sh. You might use any of these, though some (which?) depend on how your PATH is set:
./temp2.sh
temp2.sh
sh temp2.sh
sh -x temp2.sh
$HOME/randomFolder/temp2.sh
~/randomFolder/temp2.sh

creating bash script to automate task for analyzing multiple files

I don't have a lot of experience with scripting.
I have a directory that contains, among many other files, a set of *.phylip files I need to analyze with a program. I would like to automate this task. I think a loop bash shell script would be appropriate, although I could be wrong.
If I was to perform the analysis manually on one .phylip file, I would use the following command in terminal:
./raxmlHPC-SSE3 -m GTRCAT -y -s uce-5.phylip --print-identical-sequences -p 12345 -n uce-5_result
For the bash shell script, I think it would be close to:
#!/bin/sh
for i in $( ls ); do
./raxmlHPC-SSE3 -m GTRCAT -y -s uce-5.phylip --print-identical-sequences -p 12345 -n test_5 $i
done
The issue I'm aware of, but don't know how to fix, is the -s option, which specifies the input phylip file. Any suggestions on how to modify the script to do what I need done?
Try the below code:
#!/bin/bash
for i in *.phylip
do
./raxmlHPC-SSE3 -m GTRCAT -y -s "$i" --print-identical-sequences -p 12345 -n ${i%.phylip}_result
done
-s option will be passed $i which has the file name with .phylip extension in the current directory.
${i%.phylip}_result replaces the .phylip extension with _result which i guess is what you expect. (Ref: Parameter Substitution)

Trying with piping commands into an if statement

I have a bash script that puts a bunch of commands to make a directory into a text file. Then it cats the file into sh to run the commands. What I am trying to do is only run the command if the directory doesn't already exist.
Here is what I have:
A text file with something like this:
mkdir /path/to/a/directory
mkdir /path/to/another/directory
mkdir /path/to/yet/another/directory
In my script I have a line like this
cat /path/to/my/file.txt | sh
But is there a way to do something like this?
cat /path/to/my/file.txt | if path already exists then go to the next, if not | sh
In other words I would like to skip the attempt to make the directory if the path already exists.
Update: The OP has since clarified that use of mkdir is just an example, and that he needs a generic mechanism to conditionally execute lines from a text file containing shell commands, based on whether the commands refers to an existing directory or not:
while read -r cmd dir; do [[ -d $dir ]] || eval "$cmd $path"; done < /path/to/my/file.txt
The while loop reads the text file containing the shell commands line by line.
read -r cmd dir parses each line into the first token - assumed to be the command (mkdir in the sample input) - and the rest, assumed to be the directory path.
[[ -d $dir ]] tests the existence of the directory path, and || only executes its RHS if the test fails, i.e., if the directory does not exist.
eval "$cmd $path" then executes the line; note that use of eval here is not any less secure than piping to sh - in both cases you must trust the strings representing the commands. (Using eval from the current Bash shell means that Bash will execute the command, not sh, but I'm assuming that's not a problem.)
Original answer, based on the assumption that mkdir is actually used:
The simplest approach in your case is to add the -p option to your mkdir calls, which will quietly ignore attempts to create a directory that already exists:
mkdir -p /path/to/a/directory
mkdir -p /path/to/another/directory
mkdir -p /path/to/yet/another/directory
To put it differently: mkdir -p ensures existence of the target dir., whether that dir. already exists or has to be created.
(mkdir -p can still fail, such as when the target path is a file rather than a dir., or if you have insufficient permissions to create the dir.)
You can then simply pass the file to sh (no need for cat and a pipe, which is less efficient):
sh /path/to/my/file.txt
In case you do not control creation of the input file, you can use sed to insert the -p option:
sed 's/^mkdir /&-p /' /path/to/my/file.txt | sh
I'm not clear if you want to check for the existence of files or directories.. but here's how to to it:
Run your command if the file exists:
[ -f /path/to/my/file.txt ] && cat /path/to/my/file.txt | sh
or to check for directories:
[ -d /path/to/my/directory ] && cat /path/to/my/file.txt | sh
Write your own mkdir function.
Assuming your file doesn't use mkdir -p anywhere this should work.
mkdir() {
for dir; do
[ -d "$dir" ] || mkdir "$dir"
done
}
export -f mkdir
sh < file

Triple nested quotations in shell script

I'm trying to write a shell script that calls another script that then executes a rsync command.
The second script should run in its own terminal, so I use a gnome-terminal -e "..." command. One of the parameters of this script is a string containing the parameters that should be given to rsync. I put those into single quotes.
Up until here, everything worked fine until one of the rsync parameters was a directory path that contained a space. I tried numerous combinations of ',",\",\' but the script either doesn't run at all or only the first part of the path is taken.
Here's a slightly modified version of the code I'm using
gnome-terminal -t 'Rsync scheduled backup' -e "nice -10 /Scripts/BackupScript/Backup.sh 0 0 '/Scripts/BackupScript/Stamp' '/Scripts/BackupScript/test' '--dry-run -g -o -p -t -R -u --inplace --delete -r -l '\''/media/MyAndroid/Internal storage'\''' "
Within Backup.sh this command is run
rsync $5 "$path"
where the destination $path is calculated from text in Stamp.
How can I achieve these three levels of nested quotations?
These are some question I looked at just now (I've tried other sources earlier as well)
https://unix.stackexchange.com/questions/23347/wrapping-a-command-that-includes-single-and-double-quotes-for-another-command
how to make nested double quotes survive the bash interpreter?
Using multiple layers of quotes in bash
Nested quotes bash
I was unsuccessful in applying the solutions to my problem.
Here is an example. caller.sh uses gnome-terminal to execute foo.sh, which in turn prints all the arguments and then calls rsync with the first argument.
caller.sh:
#!/bin/bash
gnome-terminal -t "TEST" -e "./foo.sh 'long path' arg2 arg3"
foo.sh:
#!/bin/bash
echo $# arguments
for i; do # same as: for i in "$#"; do
echo "$i"
done
rsync "$1" "some other path"
Edit: If $1 contains several parameters to rsync, some of which are long paths, the above won't work, since bash either passes "$1" as one parameter, or $1 as multiple parameters, splitting it without regard to contained quotes.
There is (at least) one workaround, you can trick bash as follows:
caller2.sh:
#!/bin/bash
gnome-terminal -t "TEST" -e "./foo.sh '--option1 --option2 \"long path\"' arg2 arg3"
foo2.sh:
#!/bin/bash
rsync_command="rsync $1"
eval "$rsync_command"
This will do the equivalent of typing rsync --option1 --option2 "long path" on the command line.
WARNING: This hack introduces a security vulnerability, $1 can be crafted to execute multiple commands if the user has any influence whatsoever over the string content (e.g. '--option1 --option2 \"long path\"; echo YOU HAVE BEEN OWNED' will run rsync and then execute the echo command).
Did you try escaping the space in the path with "\ " (no quotes)?
gnome-terminal -t 'Rsync scheduled backup' -e "nice -10 /Scripts/BackupScript/Backup.sh 0 0 '/Scripts/BackupScript/Stamp' '/Scripts/BackupScript/test' '--dry-run -g -o -p -t -R -u --inplace --delete -r -l ''/media/MyAndroid/Internal\ storage''' "

Resources