Im trying to cd into the md5 hash of whatever variable is set into the script but I do not get the correct value of md5, I think it has something to do with how I'm declaring my variables. Thank you for any help!
#!/bin/bash
var1=$1
md5=$(-n $var1 | md5sum)
cd /var/www/html/$md5
I expected it to take me to a directory given by the md5 hash:
$ ./myscript hello
(no output)
$ pwd
/var/www/html/5d41402abc4b2a76b9719d911017c592
Instead, it gives me errors and tries to cd to the wrong path:
$ ./myscript hello
./myscript: line 3: -n: command not found
./myscript: line 4: cd: /var/www/html/d41d8cd98f00b204e9800998ecf8427e: No such file or directory
$ pwd
/home/me
The md5sum it incorrectly tries to cd to is also the same no matter which value I input.
This works as a solution for anyone else having this issue
#!/bin/bash
md5=$*
hash="$(echo -n "$md5" | md5sum )"
cd /var/www/html/$hash
Your script:
#!/bin/bash
var1=$1
md5=$(-n $var1 | md5sum)
cd /var/www/html/$md5
This has a few issues:
-n is not a valid command in the pipeline -n $var1 | md5sum.
md5sum returns more than just the MD5 digest.
Changing the directory in a script will not be reflected in the calling shell.
Input is used unquoted.
I would write a shell function for this, rather than a script:
function md5cd {
dir="$( printf "%s" "$1" | md5sum - | cut -d ' ' -f 1 )"
cd /var/www/html/"$dir" || return 1
}
The function computes the MD5 digest of the given string using md5sum and cuts off the filename (-) that's part of the output. It then changes directory to the specified location. If the target directory does not exist, it signals this by returning a non-zero exit status.
Extending it to cd to a path constructed from the path on the command line, but with the last path element changed into a MD5 digest (just for fun):
function md5cd {
word="${1##*/}"
if [[ "$word" == "$1" ]]; then
prefix="."
else
prefix="${1%/*}"
fi
dir="$( cut -d ' ' -f 1 <( printf "%s" "$word" | md5sum - ) )"
cd "$prefix"/"$dir" || return 1
}
Testing it:
$ pwd
/home/myself
$ echo -n "hex this" | md5sum
990c0fc93296f9eed6651729c1c726d4 -
$ mkdir /tmp/990c0fc93296f9eed6651729c1c726d4
$ md5cd /tmp/"hex this"
$ pwd
/tmp/990c0fc93296f9eed6651729c1c726d4
Related
I'm trying to find a command on bash shell, which allows me to verify if all words given in parameter(in the list $*), exist in the current directory I'm in.
Exemple, if I execute this command:
bash ./exp_quotes.sh hadir Trex blabla
How to test the existence of the tree words in one command, and get a value of 1 or 0 as $? ?
If you want to check if all patterns exist in file,
you can write exp_quotes.sh like this:
#!/usr/local/env bash
for arg; do
grep -q "$arg" file || exit 1
done
This script will exit with 1 (failure) if any of the arguments is not in file.
Otherwise it will exit with 0 (success).
You can make grep do all the job of searching for all patterns.
You just need to concatenate all of them together with |, as this:
$ echo "$(IFS=\|; echo "$*")"
hadir|Trex|blabla
Then, you just need to use grep to do the search:
$ cat ./exp_quotes.sh
#!/bin/bash
file="$1"
grep -qE "\"$(IFS=\|; echo "$*")\"" "$file" && exit 1 || exit 0
Change the permissions (to run) of the script:
$ chmod u+x ./exp_quotes.sh
And execute it with the filename first and the patterns:
$ ./exp_quotes.sh file hadir Trex blabla
This may be used even for patterns with spaces:
$ ./exp_quotes.sh "file name with spaces" "a hadir with spaces" Trex blabla
If what you need is to list the files that do contain all the words, use this:
$ cat ./exp_quotes.sh
#!/bin/bash
grep -lE "\"$(IFS=\|; echo "$*")\"" *
To have an exit code of 0 if "at least one word is found" is the same as "exit 0 if one word OR other is found"
That would be:
#!/bin/bash
infile="$1"
grep -qE "\"$(IFS=\|; echo "$*")\"" infile && exit 0 || exit 1
I have CentOS and this bash script:
#!/bin/sh
files=$( ls /vps_backups/site )
counter=0
for i in $files ; do
echo $i | grep -o -P '(?<=-).*(?=.tar)'
let counter=$counter+1
done
In the site folder I have compressed backups with the following names :
site-081916.tar.gz
site-082016.tar.gz
site-082116.tar.gz
...
The code above prints :
081916
082016
082116
I want to put each extracted date to a variable so I replaced this line
echo $i | grep -o -P '(?<=-).*(?=.tar)'
with this :
dt=$($i | grep -o -P '(?<=-).*(?=.tar)')
echo $dt
however I get this error :
./test.sh: line 6: site-090316.tar.gz: command not found
Any help please?
Thanks
you still need the echo inside the $(...):
dt=$(echo $i | grep -o -P '(?<=-).*(?=.tar)')
Don't use ls in a script. Use a shell pattern instead. Also, you don't need to use grep; bash has a built-in regular expression operator.
#!/bin/bash
files=$( /vps_backups/site/* )
counter=0
for i in "${files[#]#/vps_backups/site/}" ; do
[[ $i =~ -(.*).tar.gz ]] && dt=${BASH_REMATCH[1]}
counter=$((counter + 1))
done
I have written a shell script which SSH to a remote host and does some processing. The code that executes remotely has to to use the local variables which are read from the properties file. My code is as below. The below code is not executed properly. Its giving an error that
-printf: unknown primary or command.
Please help me with this.
Note: datadir, username and ftphostname are defined in properties file.
. config.properties
ssh $username#$ftphostname << EOF
filelist=;
filelist=($(find "$datadir" -type f -printf "%T# %p\n"| sort -n | head -5 | cut -f2- -d" "));
filecount=\${#filelist[#]};
while [ \${#filelist[#]} -gt 0 ]; do
checkCount=;
filesSize=$(wc -c \${filelist[#]}|tail -n 1 | cut -d " " -f1) ;
if [ "\$filesSize" == "\$fileSizeStored" ]; then
fileSizeStored=0;
printf "\n*********** \$(date) ************* " >> /home/chisan/logs/joblogs.log;
echo "Moved below files" >> /home/joblogs.log;
for i in "\${filelist[#]}"
do
# echo "file is \$i"
checkCount=0;
mv \$i /home/outputdirectory/;
if [ $? -eq 0 ]; then
echo "File Moved to the server: \$i" >> /home/joblogs.log;
else
echo "Error: Failed to move file: \$i" >> /home/joblogs.log;
fi
done
filelist=($(find "$datadir" -type f -printf '%T# %p\n' | sort -n | head -5 | cut -f2- -d" "));
else
((checkCount+=1));
sleep 4;
fileSizeStored=\$filesSize;
fi
done
EOF
But this one works
#ssh to remote system and sort the files and fetch the files which are copied first(based on modification time)
ssh -o StrictHostKeyChecking=no user#server 'filelist=($(find /home/data -type f - printf "%T# %p\n" | sort -n | head -5 | cut -f2- -d" "));
# filelist array variable holds the file names which have the oldest modification date.
#check the directory until it has atleast one file.
while [ ${#filelist[#]} -gt 0 ]; do
filesSize=$(wc -c "${filelist[#]}"|tail -n 1 | cut -d " " -f1) ;
#filesSize contains the total size of the files that are in the filelist array.
if [ -e "$HOME/.storeFilesSize" ]; then
fileSizeStored=$(cat "$HOME/.storeFilesSize");
if [ "$filesSize" == "$fileSizeStored" ]; then
echo "Moved below files" >> /home/joblogs.log;
for i in "${filelist[#]}"
do
mv "$i" /home/dmpdata1 &>/dev/null;
if [ $? -eq 0 ]; then
echo "File Moved to the server: $i" >>/home/joblogs.log;
else
echo "Error: Failed to move file: $i" >>/home/joblogs.log;
fi
done
filelist=($(find /home/data -type f -printf "%T# %p\n" | sort -n | head -5 | cut -f2- -d" "));
else
sleep 4;
echo "$filesSize" > "$HOME/.storeFilesSize";
fi
else
echo "creating new file";
echo "$filesSize" > "$HOME/.storeFilesSize";
fi
done'
I will not answer directly (ie, not with your specific needs and actions), but give a generic possibility and how to use local and remote variables :
Your master script should create a "specific script", locally.
And then copy it over and run it remotely (with additionnal arguments if needed)
Generic example of Master script :
#local Master script: This script creates a local script,
# and then copy it to remotehost and start it
#Some local variables will be defined here.
#They can be used below, and will be replaced by their value locally
localvar1="...."
localvar2="...."
#now we create the script
cat > /tmp/localscript_to_be_copied_to_remote.sh <<EOF
#remote_script
for i in ..... ; do
something ;
somethingelse
done
......
.....
EOF
#in the above, each time you used "$localvar1" or "$localvar2", the script
# /tmp/localscript_to_be_copied_to_remote.sh will instead have their values,
# as the local shell will replace them on the fly during the cat > ... <<EOF .
# if you want to have some remotevariable "as is" (and not as their local value) in the script,
# write them as "\$remotevariable" there, instead of "$remotevariable", so the local shell
# won't interpret them during the 'cat', and the script will receive "$remotevariable"
# as is, instead of its local value.
#then you copy the script:
scp -p /tmp/localscript_to_be_copied_to_remote.sh user#remotehost:/some/dir/name.sh
#and you run it:
# UNCOMMENT the line below ONLY when /tmp/localscript_to_be_copied_to_remote.sh is correct!
# ssh user#remotehost "/some/dir/name.sh" #+ maybe some parameters as well
#end of local Master script.
You then run "local Master script" and have it create the tmp file locally (which you can check to make sure it is supposed to be like this on the remote host), and then copy it remotely and execute it.
Specific example of master script :
#!/bin/bash
local1="/tmp /var /usr /home" # this will be the default name of the dirs (on the remote host)
# that the script will print the size of (+ any additionnal parameters)
cat > /tmp/printsizes.bash <<EOF
#!/bin/bash
for dir in $local1 "\$#" ; do
du -ks "\$dir"
done
EOF
scp -p /tmp/printsizes.bash user#remotehost:/tmp/print_dir_sizes.bash
ssh user#remotehost "/tmp/print_dir_sizes.bash /etc /root"
This (weird...) example will create a LOCAL script containing:
#!/bin/bash
for dir in /tmp /var /usr /home "$#" ; do
du -ks "$dir"
done
And will execute it with:
ssh user#remotehost "/tmp/print_dir_sizes.bash /etc /root"
so it will do remotely:
for dir in /tmp /var /usr /home /etc /root ; do
du -ks "$dir"
done
I hope it helps to see how to use local and remote variables...
I found similar questions but not in Linux/Bash
I want my script to create a file with a given name (via user input) but add number at the end if filename already exists.
Example:
$ create somefile
Created "somefile.ext"
$ create somefile
Created "somefile-2.ext"
The following script can help you. You should not be running several copies of the script at the same time to avoid race condition.
name=somefile
if [[ -e $name.ext || -L $name.ext ]] ; then
i=0
while [[ -e $name-$i.ext || -L $name-$i.ext ]] ; do
let i++
done
name=$name-$i
fi
touch -- "$name".ext
Easier:
touch file`ls file* | wc -l`.ext
You'll get:
$ ls file*
file0.ext file1.ext file2.ext file3.ext file4.ext file5.ext file6.ext
To avoid the race conditions:
name=some-file
n=
set -o noclobber
until
file=$name${n:+-$n}.ext
{ command exec 3> "$file"; } 2> /dev/null
do
((n++))
done
printf 'File is "%s"\n' "$file"
echo some text in it >&3
And in addition, you have the file open for writing on fd 3.
With bash-4.4+, you can make it a function like:
create() { # fd base [suffix [max]]]
local fd="$1" base="$2" suffix="${3-}" max="${4-}"
local n= file
local - # ash-style local scoping of options in 4.4+
set -o noclobber
REPLY=
until
file=$base${n:+-$n}$suffix
eval 'command exec '"$fd"'> "$file"' 2> /dev/null
do
((n++))
((max > 0 && n > max)) && return 1
done
REPLY=$file
}
To be used for instance as:
create 3 somefile .ext || exit
printf 'File: "%s"\n' "$REPLY"
echo something >&3
exec 3>&- # close the file
The max value can be used to guard against infinite loops when the files can't be created for other reason than noclobber.
Note that noclobber only applies to the > operator, not >> nor <>.
Remaining race condition
Actually, noclobber does not remove the race condition in all cases. It only prevents clobbering regular files (not other types of files, so that cmd > /dev/null for instance doesn't fail) and has a race condition itself in most shells.
The shell first does a stat(2) on the file to check if it's a regular file or not (fifo, directory, device...). Only if the file doesn't exist (yet) or is a regular file does 3> "$file" use the O_EXCL flag to guarantee not clobbering the file.
So if there's a fifo or device file by that name, it will be used (provided it can be open in write-only), and a regular file may be clobbered if it gets created as a replacement for a fifo/device/directory... in between that stat(2) and open(2) without O_EXCL!
Changing the
{ command exec 3> "$file"; } 2> /dev/null
to
[ ! -e "$file" ] && { command exec 3> "$file"; } 2> /dev/null
Would avoid using an already existing non-regular file, but not address the race condition.
Now, that's only really a concern in the face of a malicious adversary that would want to make you overwrite an arbitrary file on the file system. It does remove the race condition in the normal case of two instances of the same script running at the same time. So, in that, it's better than approaches that only check for file existence beforehand with [ -e "$file" ].
For a working version without race condition at all, you could use the zsh shell instead of bash which has a raw interface to open() as the sysopen builtin in the zsh/system module:
zmodload zsh/system
name=some-file
n=
until
file=$name${n:+-$n}.ext
sysopen -w -o excl -u 3 -- "$file" 2> /dev/null
do
((n++))
done
printf 'File is "%s"\n' "$file"
echo some text in it >&3
Try something like this
name=somefile
path=$(dirname "$name")
filename=$(basename "$name")
extension="${filename##*.}"
filename="${filename%.*}"
if [[ -e $path/$filename.$extension ]] ; then
i=2
while [[ -e $path/$filename-$i.$extension ]] ; do
let i++
done
filename=$filename-$i
fi
target=$path/$filename.$extension
Use touch or whatever you want instead of echo:
echo file$((`ls file* | sed -n 's/file\([0-9]*\)/\1/p' | sort -rh | head -n 1`+1))
Parts of expression explained:
list files by pattern: ls file*
take only number part in each line: sed -n 's/file\([0-9]*\)/\1/p'
apply reverse human sort: sort -rh
take only first line (i.e. max value): head -n 1
combine all in pipe and increment (full expression above)
Try something like this (untested, but you get the idea):
filename=$1
# If file doesn't exist, create it
if [[ ! -f $filename ]]; then
touch $filename
echo "Created \"$filename\""
exit 0
fi
# If file already exists, find a similar filename that is not yet taken
digit=1
while true; do
temp_name=$filename-$digit
if [[ ! -f $temp_name ]]; then
touch $temp_name
echo "Created \"$temp_name\""
exit 0
fi
digit=$(($digit + 1))
done
Depending on what you're doing, replace the calls to touch with whatever code is needed to create the files that you are working with.
This is a much better method I've used for creating directories incrementally.
It could be adjusted for filename too.
LAST_SOLUTION=$(echo $(ls -d SOLUTION_[[:digit:]][[:digit:]][[:digit:]][[:digit:]] 2> /dev/null) | awk '{ print $(NF) }')
if [ -n "$LAST_SOLUTION" ] ; then
mkdir SOLUTION_$(printf "%04d\n" $(expr ${LAST_SOLUTION: -4} + 1))
else
mkdir SOLUTION_0001
fi
A simple repackaging of choroba's answer as a generalized function:
autoincr() {
f="$1"
ext=""
# Extract the file extension (if any), with preceeding '.'
[[ "$f" == *.* ]] && ext=".${f##*.}"
if [[ -e "$f" ]] ; then
i=1
f="${f%.*}";
while [[ -e "${f}_${i}${ext}" ]]; do
let i++
done
f="${f}_${i}${ext}"
fi
echo "$f"
}
touch "$(autoincr "somefile.ext")"
without looping and not use regex or shell expr.
last=$(ls $1* | tail -n1)
last_wo_ext=$($last | basename $last .ext)
n=$(echo $last_wo_ext | rev | cut -d - -f 1 | rev)
if [ x$n = x ]; then
n=2
else
n=$((n + 1))
fi
echo $1-$n.ext
more simple without extension and exception of "-1".
n=$(ls $1* | tail -n1 | rev | cut -d - -f 1 | rev)
n=$((n + 1))
echo $1-$n.ext
code:
path=$PATH:
while [ -n $path ]
do
ls -ld ${path%%:*}
path=${path#*:}
done
I want to get the each part of path .When run the script ,it can not get out of the while process 。Please tell me why . Is some problem in 'while [ -n $path ]' ?
The final cut never results in an empty string. If you have a:b:c, you'll strip off the a and then the b, but never the c. I.e., this:
${path#*:}
Will always result in a non-empty string for the last piece of the path. Since the -n check looks for an empty string, your loop runs forever.
If $path doesn't have a colon in it, ${path#*:} will return $path. So you have an infinite loop.
p="foo"
$ echo ${p#*:}
foo
$ p="foo:bar"
$ echo ${p#*:}
bar
You have some bugs in your code. This should do the trick:
path=$PATH
while [[ $path != '' ]]; do
# you can replace echo to whatever you need, like ls -ld
echo ${path%%:*}
if echo $path | grep ':' >/dev/null; then
path=${path#*:}
else path=''
fi
done
Your path, after is initialized, will always check True for [ -n path ] test. This is the main reason for which you never get out of the while loop.