Bash : create file with name from function parameter [duplicate] - bash

This question already has answers here:
"~/Desktop/test.txt: No such file or directory"
(2 answers)
Closed 6 years ago.
I want to append things to a file in a way that uses sudo if needed, here's what I have so far :
getAndAppend(){
# create file if doesn't exist, with right permission
[[ ! -s $2 ]] && touch "$2" || [[ ! -s $2 ]] && sudo touch "$2" # line 1
# append stuff to it
[[ -w $2 ]] && curl -sSL $1 >> $2 || sudo bash -c "curl -sSL $1 >> $2"
[[ -w $2 ]] && echo -e "\n" >> $2 || sudo bash -c "echo -e \"\n\""
}
file="~/.wot"
url="https://raw.github.com/n-marshall/system-setup/master/common/configs/.gitignore_global"
getAndAppend $url $file
However line 1 doesn't work in that the output will be something like ~/.wot: No such file or directory
Then of course the file won't exist and the following lines cannot work properly.
How could I fix that ? Thank you ! Any other comment or approach is welcome of course !

file=~/.wot
or
file="$HOME/.wot"
Quotes prevent tilde expansion.
By the way -- you can do better than plastering sudo in front of every command that needs to emit output to your destination file. Consider:
# Note that this requires bash 4.1 for automatic FD allocation
# otherwise, modify it to hardcode a FD number for our backup.
withOutputToFile() {
local orig_stdout retval
exec {orig_stdout}>&1 || return
if ! exec >"$1"; then
if ! exec > >(sudo tee -- "$1"); then
exec >&$orig_stdout
return 1
fi
fi
shift
"$#"; retval=$?
exec >&$orig_stdout {orig_stdout}>&-
return "$retval"
}
That's a mouthful, sure, but once you have it you can use it to wrap any function:
getAndAppend() {
curl "$1" && printf '\n'
}
withOutputToFile /path/to/your/file getAndAppend http://example.com/

Related

unable to comare the output of the function in shell script [duplicate]

This question already has answers here:
Bash script store command output into variable
(2 answers)
Closed 2 years ago.
I am trying to remove docker container and and store or compare the result but i am unable to do that kindly let me know how to get the return value for further process
delete_the_container() {
res= docker container rm -f $1 && echo "success" || echo "fail"
echo "$?"
}
fielpos="./filename"
input="./filename"
while IFS= read -r line
do
#echo "$line"
res= $(delete_the_container $line)
echo "$res" #not get the res value
done < "$input"
$? returns code value of last command, the code below must help you
docker container rm -f $1
if [[ $? == 0 ]]; then
... code if rm command success ...
else
... code if rm command failed...
fi
Your line (I added $(...) to execute command) :
res= $(docker container rm -f $1 && echo "success" || echo "fail")
res value can be "success" or "fail" but $? are all time 1 because last command are echo.

Bash associative arrays error

I seem to have this problem. This code breaks at line 119 in my script with bash associative arrays. I am sorry for the comments but I am kind to new to bash scripting. This is the code:
#!/bin/bash
# Aliases file
# Command usage: cpRecent/mvRecent -d {dirFrom},{dirTo} -n {numberofFiles} -e {editTheNames}
# Error codes
NO_ARGS="You need to pass in an argument"
INVALID_OPTION="Invaild option:"
NO_DIRECTORY="No directory found"
# Return values
fullpath=
directories=
numfiles=
interactive=
typeset -a files
typeset -A filelist
# Advise that you use relative paths
__returnFullPath(){
local npath
if [[ -d $1 ]]; then
cd "$(dirname $1)"
npath="$PWD/$(basename $1)"
npath="$npath/" #Add a slash
npath="${npath%.*}" #Delete .
fi
fullpath=${npath:=""}
}
__usage(){
wall <<End-Of-Message
________________________________________________
<cpRecent/mvRecent> -d "<d1>,<d2>" -n <num> [-i]
-d First flag: Takes two arguments
-n Second flag: Takes one argument
-i Takes no arguments. Interactive mode
d1 Directory we are reading from
d2 Directory we are writing to
num Number of files
________________________________________________
End-Of-Message
}
__processOptions(){
while getopts ":d:n:i" opt; do
case $opt in
d ) IFS=',' read -r -a directories <<< "$OPTARG";;
n ) numfiles=$OPTARG;;
i ) interactive=1;;
\? ) echo "$INVALID_OPTION -$OPTARG" >&2 ; return 1;;
: ) echo "$NO_ARGS"; __usage; return 1;;
* ) __usage; return 1;;
esac
done
}
__getRecentFiles(){
# Check some conditions
(( ${#directories[#]} != 2 )) && echo "$INVALID_OPTION Number of directories must be 2" && return 2
#echo ${directories[0]} ${directories[1]}
# Get the full paths of the directories to be read from/written to
__returnFullPath "${directories[0]}"
directories[0]="$fullpath"
__returnFullPath "${directories[1]}"
directories[1]="$fullpath"
if [[ -z ${directories[0]} || -z ${directories[1]} ]]; then
echo $NO_DIRECTORY
return 3
fi
[[ numfiles != *[!0-9]* ]] && echo "$INVALID_OPTION Number of files cannot be a string" && return 4
#numfiles=$(($numfiles + 0))
(( $numfiles == 0 )) && echo "$INVALID_OPTION Number of files cannot be zero" && return 4
local num="-"$numfiles""
# Get the requested files in directory(skips directories)
if [[ -n "$(ls -t ${directories[0]} | head $num)" ]]; then
# For some reason using local -a or declare -a does not seem to split the string into two
local tempfiles=($(ls -t ${directories[0]} | head $num))
#IFS=' ' read -r -a tempfiles <<< "$string"
#echo ${tempfiles[#]}
for index in "${!tempfiles[#]}"; do
echo $index ${tempfiles[index]}
[[ -f "${directories[0]}${tempfiles[index]}" ]] && files+=("${tempfiles[index]}")
done
fi
}
####################################
# The problem is this piece of code
__processLines(){
local name
local answer
local dirFrom
local dirTo
if [[ -n $interactive ]]; then
for (( i=0; i< ${#files[#]}; i++ )); do
name=${files[i]}
read -n 1 -p "Old name: $name. Do you wish to change the name(y/n)?" answer
[[ answer="y" ]] && read -p "Enter new name:" name
dirFrom="${directories[0]}${files[i]}"
dirTo="${directories[1]}$name"
fileslist["$dirFrom"]="$dirTo"
done
else
for line in $files; do
dirFrom="${directories[0]}$line"
echo $dirFrom # => /home/reclusiarch/Documents/test
dirTo="${directories[1]}$line"
echo $dirTo # => /home/reclusiarch/test
fileslist["$dirFrom"]="$dirTo" # This is the offending line
done
fi
}
###########################################################
cpRecent(){
__processOptions $*
__getRecentFiles
__processLines
for line in "${!filelist[#]}"; do
cp $line ${filelist[$line]}
done
echo "You have copied ${#fileList[#]} files"
unset files
unset filelist
return
}
mvRecent(){
__processOptions $*
__getRecentFiles
__processLines
for line in "${!filelist[#]}"; do
mv $line ${filelist[$line]}
done
echo "You have copied ${#fileList[#]} files"
unset files
unset filelist
return
}
cpRecent "$*"
I have tried a lot of things. To run the script,
$ bash -x ./testing.sh -d "Documents,." -n 2
But nothing seems to work:
The error is this(when using bash -x):
./testing.sh: line 119: /home/reclusiarch/Documents/test: syntax error: operand expected (error token is "/home/reclusiarch/Documents/test")
If I run that section on the command line, it works:
$ typeset -A filelist
$ filelist["/home/reclusiarch/Documents/test"]=/home/reclusiarch/test
$ echo ${filelist["/home/reclusiarch/Documents/test"]}
/home/reclusiarch/test
Thanks for your help!!
Edit: I intially pared down the script to the piece of offending code but that might make it not run. Again, if you want to test it, you could run the bash command given. (The script ideally would reside in the user's $HOME directory).
Edit: Solved (Charles Duffy solved it) It was a simple mistake of forgetting which name was which.
Your declaration is:
typeset -A filelist
However, your usage is:
fileslist["$dirFrom"]="$dirTo"
fileslist is not filelist.

How can I create a shell alias for creating a new script and opening it in the editor?

Shell script shortcuts or aliases are very useful for automating repetitive tasks in very few key strokes.
One example of example of "shortcut" command is take from zsh which does mkdir $dir && cd $dir.
I want to make something similar (medit) that does this: when called medit script.sh:
creates script
makes it executable (chmod +x)
populates it with shebang line
opens the file in the default editor
Can I do this using an alias so I would avoid writing a bash script that does that?
You better write a function like:
function medit(){
echo "#!/bin/bash" > "$1"
chmod +x "$1"
if [ -n "$VISUAL" ]; then
"$VISUAL" "$1"
elif [ -n "$EDITOR" ]; then
"$EDITOR" "$1"
else
vi "$1"
fi
}
Put it to .bashrc and call it with medit script.sh
It will first try to run the editor specified in $VISUAL and $EDITOR and falls back to vi if there is no standard editor specified.
I would write something like this:
# Usage: just like 'builtin printf'
fatal_error() {
builtin printf "$#" >&2
exit 1
}
# Usage: medit [file] [more arguments]
medit() {
# If we have arguments
if [ $# -gt 0 ]; then
# If $1 file does not exist, or is empty,
# put a shebang line into it. See `help test`.
if [ ! -s "$1" ]; then
printf "%s\n" "#!/bin/bash -" > "$1" || \
fatal_error "Failed to write to %s\n" "$1"
fi
# Make $1 executable
chmod +x "$1" || fatal_error "failed to chmod %s\n" "$1"
fi
# Run the default editor with the arguments passed to this function
"${EDITOR:-${VISUAL:-vi}}" "$#"
}
The medit command can be invoked just like the default editor, even without arguments.
On the off-chance that you don't know how to re-use the script above, put the code into some ~/scripts/medit, and source it from the appropriate initialization script such as ~/.bashrc: source ~/scripts/medit.
What you are asking for is an alias.
There is no way to do all you ask without using one or several functions.
But there is a way to make an alias define some functions and also call them:
alias medit='
SayError(){ local a=$1; shift; printf "%s\n" "$0: $#" >&2; exit "$a"; }
medit(){
[[ $# -lt 1 ]] &&
SayError 1 "We need at least the name of the file as an argument"
[[ ! -s $1 ]] && echo "#!/bin/bash" > "$1" ||
SayError 2 "File $1 already exists"
chmod u+x "$1" ||
SayError 3 "File $1 could not be made executable"
${VISUAL:-${EDITOR:-emacs}} "$1" ||
SayError 4 "File $1 could not be open in the editor"
}
\medit'
You need to execute the above definition of the alias medit or place it in ~/.bashrc, or simply source it in the running shell to make it exist.
Then, when the alias is called, it defines two functions: SayError and medit.
Yes, a function with the same name as the alias: medit.
After the function definition, the alias will call the function by using a trick:
\medit
As (strictly speaking) a \medit is not exactly the alias medit, bash keeps searching and finds the function medit, which by then has been defined and is executed.
Of course, you can just define the functions and use them without resorting to an alias to make the definition of the functions, that's your choice.
What is nice is to have the choice. :)
This is how you could define all in a sourced file:
alias medit='\medit'
SayError(){ local a=$1; shift; printf "%s\n" "$0: $#" >&2; exit "$a"; }
medit(){
[[ $# -lt 1 ]] &&
SayError 1 "We need at least the name of the file as an argument"
[[ ! -s $1 ]] && echo "#!/bin/bash" > "$1" ||
SayError 2 "File $1 already exists"
chmod u+x "$1" ||
SayError 3 "File $1 could not be made executable"
${VISUAL:-${EDITOR:-emacs}} "$1" ||
SayError 4 "File $1 could not be open in the editor"
}

Bash scripting: syntax error near unexpected token `done'

I am new to bash scripting and I have to create this script that takes 3 directories as arguments and copies in the third one all the files in the first one that are NOT in the second one.
I did it like this:
#!/bin/bash
if [ -d $1 && -d $2 && -d $3 ]; then
for FILE in [ ls $1 ]; do
if ! [ find $2 -name $FILE ]; then
cp $FILE $3
done
else echo "Error: one or more directories are not present"
fi
The error I get when I try to execute it is: "line 7: syntax error near unexpected token `done' "
I don't really know how to make it work!
Also even if I'm using #!/bin/bash I still have to explicitly call bash when trying to execute, otherwise it says that executing is not permitted, anybody knows why?
Thanks in advance :)
Couple of suggestions :
No harm double quoting variables
cp "$FILE" "$3" # prevents wordsplitting, helps you filenames with spaces
for statement fails for the fundamental reason -bad syntax- it should've been:
for FILE in ls "$1";
But then, never parse ls output. Check [ this ].
for FILE in ls "$1"; #drastic
Instead of the for-loop in step2 use a find-while-read combination:
find "$1" -type f -print0 | while read -rd'' filename #-type f for files
do
#something with $filename
done
Use lowercase variable names for your script as uppercase variables are reserved for the system. Check [this].
Use tools like [ shellcheck ] to improve script quality.
Edit
Since you have mentioned the input directories contain only files, my alternative approach would be
[[ -d "$1" && -d "$2" && -d "$3" ]] && for filename in "$1"/*
do
[ ! -e "$2/${filename##*/}" ] && cp "$filename" "$3"
done
If you are baffled by ${filename##*/} check [ shell parameter expansion ].
Sidenote: In linux, although discouraged it not uncommon to have non-standard filenames like file name.
Courtesy: #chepner & #mklement0 for their comments that greatly improved this answer :)
Your script:
if ...; then
for ...; do
if ...; then
...
done
else
...
fi
Fixed structure:
if ...; then
for ...; do
if ...; then
...
fi # <-- missing
done
else
...
fi
If you want the script executable, then make it so:
$ chmod +x script.sh
Notice that you also have other problems in you script. It is better written as
dir1="$1"
dir2="$2"
dir3="$3"
for f in "$dir1"/*; do
if [ ! -f "$dir2/$(basename "$f")" ]; then
cp "$f" "$dir3"
fi
done
this is not totally correct:
for FILE in $(ls $1); do
< whatever you do here >
done
There is a big problem with that loop if in that folder there is a filename like this: 'I am a filename with spaces.txt'.
Instead of that loop try this:
for FILE in "$1"/*; do
echo "$FILE"
done
Also you have to close every if statement with fi.
Another thing, if you are using BASH ( #!/usr/bin/env bash ), it is highly recommended to use double brackets in your test conditions:
if [[ test ]]; then
...
fi
For example:
$ a='foo bar'
$ if [[ $a == 'foo bar' ]]; then
> echo "it's ok"
> fi
it's ok
However, this:
$ if [ $a == 'foo bar' ]; then
> echo "it's ok";
> fi
bash: [: too many arguments
You've forgot fi after the innermost if.
Additionally, neither square brackets nor find do work this way. This one does what your script (as it is now) is intended to on my PC:
#!/bin/bash
if [[ -d "$1" && -d "$2" && -d "$3" ]] ; then
ls -1 "$1" | while read FILE ; do
ls "$2/$FILE" >/dev/null 2>&1 || cp "$1/$FILE" "$3"
done
else echo "Error: one or more directories are not present"
fi
Note that after a single run, when $2 and $3 refer to different directories, those files are still not present in $2, so next time you run the script they will be copied once more despite they already are present in $3.

How can I run `done < <(curl -sL file.txt);` in older versions (1993-12-28) of ksh or update to bash?

I have a script that runs flawlessly on many of the servers required. But recently it's failed on servers with old ksh versions.
Can you help me fix the offending line:
#!/bin/ksh
SUCCESS=0
FAILURE=0
while read IP
do
CURL=$(curl -s -m 2 -x http://$IP -L http://icanhazip.com)
if [[ "${IP%%:*}" == "${CURL%%:*}" ]] ; then
SUCCESS=$[SUCCESS+1]
echo "$IP ✓"
else
FAILURE=$[FAILURE+1]
echo "$IP X"
fi
done < <(curl -sL vpn-proxy-list.txt);
echo "✓: $SUCCESS X: $FAILURE"
The final line returns:
line 3: syntax error at line 14: `<(' unexpected
Unfortunately I'm unable to update ksh.
Can you help me make the done < <(curl -sL vpn-proxy-list.txt); portion simply work in bash? Or compatible with older versions (1993) of ksh?
You don't appear to be doing anything in the while body that cause trouble if it was run in a subshell, so I'd just stick with a plain pipline:
#!/bin/ksh
curl -sL vpn-proxy-list.txt | while read -r ip; do
output=$(curl -s -m 2 -x "http://$ip" -L http://icanhazip.com)
if [[ "${ip%%:*}" == "${output%%:*}" ]]; then
echo "$ip Y"
else
echo "$ip X"
fi
done
Now you're asking something that breaks because you're making variable changes in a subshell, and those variables disappear when the subshell exits. A workaround: use grouping braces
curl -sL vpn-proxy-list.txt | {
success=0
failure=0
while read -r ip; do
output=$(curl -s -m 2 -x "http://$ip" -L http://icanhazip.com)
if [[ "${ip%%:*}" == "${output%%:*}" ]]; then
echo "$ip Y"
let success+=1
else
echo "$ip X"
let failure+=1
fi
done
echo there were $success successes
echo there were $failure failures
}
# variables "success" and "failure" don't exist here.
You could make use of named pipes.
mkfifo foobar
curl -sL vpn-proxy-list.txt > foobar &
# Maybe sleep for a while here
while read -r IP; do
# do something here
done < foobar
rm foobar

Resources