I have written a script to change file ownerships based on an input list read in. My script works fine on directories without space in their name. However it fails to change files on directories with space in their name. I also would like to capture the output from the chown command to a file. Could anyone help ?
here is my script in ksh:
#!/usr/bin/ksh
newowner=eg27395
dirname=/home/sas/sastest/
logfile=chowner.log
date > $dir$logfile
command="chown $newowner:$newowner"
for fname in list
do
in="$dirname/$fname"
if [[ -e $in ]]
then
while read line
do
tmp=$(print "$line"|awk '{if (substr($2,1,1) == "/" ) print $2; if (substr($0,1,1) == "/" ) print '})
if [[ -e $tmp ]]
then
eval $command \"$tmp\"
fi
done < $in
else
echo "input file $fname is not present. Check file location in the script."
fi
done
a couple of other errors:
date > $dir$logfile -- no $dir variable defined
to safely read from a file: while IFS= read -r line
But to answer your main concern, don't try to build up the command so dynamically: don't bother with the $command variable, don't use eval, and quote the variable.
chmod "$newowner:$newowner" "$tmp"
The eval is stripping the quotes on this line
command="chown $newowner:$newowner"
In order to get the line to work with spaces you will need to provide backslashed quotes
command="chown \"$newowner:$newowner\""
This way the command that eval actually runs is
chown "$newowner:$newowner"
Also, you probably need quotes around this variable setting, although you'll need to tweak the syntax
tmp="$(print "$line"|awk '{if (substr($2,1,1) == "/" ) print $2; if (substr($0,1,1) == "/" ) print '})"
To capture the output you can add 2>&1 > file.out where file.out is the name of the file ... in order to get it working with eval as you are using it you will need to backslash any special characters much in the same way you need to backslash the double quotes
Your example code suggests that list is a "meta" file: A list of files that each has a list of files to be changed. When you only have one file you can remove the while loop.
When list is a variable with filenames you need echo "${list}"| while ....
It is not completely clear why you sometimes want to start with the third field. It seems that sometimes you have 2 words before the filename and want them to be ignored. Cutting the string on spaces becomes a problem when your filenames have spaces as well. The solution is look for a space followed by a slash: that space is not part of a filename and everything up to that space can be deleted.
newowner=eg27395
# The slash on the end is not really part of the dir name, doesn't matter for most commands
dirname=/home/sas/sastest
logfile=chowner.log
# Add braces, quotes and change dir into dirname
date > "${dirname}/${logfile}"
# Line with command not needed
# Is list an inputfile? It is streamed using "< list" at the end of while []; do .. done
while IFS= read -r fname; do
in="${dirname}/${fname}"
# Quotes are important
if [[ -e "$in" ]]; then
# get the filenames with a sed construction, and give it to chmod with xargs
# The sed construction is made for the situation that in a line with a space followed by a slash
# the filename starts with the slash
# sed is with # to avoid escaping the slashes
# Do not redirect the output here but after the loop.
sed 's#.* /#/#' "${in}" | xargs chmod ${newowner}:${newowner}
else
echo "input file ${fname} is not present. Check file location in the script."
fi
done < list >> "${dirname}/${logfile}"
Related
I'm having an issue in something that seems to be a rookie error, but I can't find a way to find a solution.
I have a bash script : log.sh
which is :
#!/bin/bash
echo $1 >> log_out.txt
And with a file made of filenames (taken from the output of "find" which names is filesnames.txt and contains 53 lines of absolute paths) I try :
./log.sh $(cat filenames.txt)
the only output I have in the log_out.txt is the first line.
I need each line to be processed separately as I need to put them in arguments in a pipeline with 2 softwares.
I checked for :
my lines being terminated with /n
using a simple echo without writing to a file
all the sorts of cat filenames.txt or (< filenames.txt) found on internet
I'm sure it's a very dumb thing, but I can't find why I can't iterate more than one line :(
Thanks
It is because your ./log.sh $(cat filenames.txt) is being treated as one argument.
while IFS= read -r line; do
echo "$line";
done < filenames.txt
Edit according to: https://mywiki.wooledge.org/DontReadLinesWithFor
Edit#2:
To preserve leading and trailing whitespace in the result, set IFS to the null string.
You could simplify more and skip using explicit variable and use the default $REPLY
Source: http://wiki.bash-hackers.org/commands/builtin/read
You need to quote the command substitution. Otherwise $1 will just be the first word in the file.
./log.sh "$(cat filenames.txt)"
You should also quote the variable in the script, otherwise all the newlines will be converted to spaces.
echo "$1" >> log_out.txt
If you want to process each word separately, you can leave out the quotes
./log.sh $(cat filenames.txt)
and then use a loop in the script:
#!/bin/bash
for word in "$#"
do
echo "$word"
done >> log_out.txt
Note that this solution only works correctly when the file has one word per line and there are no wildcards in the words. See mywiki.wooledge.org/DontReadLinesWithFor for why this doesn't generalize to more complex lines.
You can iterate with each line.
#!/bin/bash
for i in $*
do
echo $i >> log_out.txt
done
How would I write a bash script that parses a text file, finding any lines that contain the word command: and then saves the entirety of each line on which it was found on to a text file?
The command would be
grep command: your_filename >> save_filename
Which is
#!/bin/bash
grep command: $1 >> $2
Executed by
scriptname your_filename save_filename
Thanks David
Note that I'm using an appender >>, instead of a create >. The latter ensures a file with only your last run in it, whereas the appender will add new lines to the file if it already exists.
If you are looking for a pure bash solution of this grep-like behaviour:
#!/bin/bash
# Usage: ./mygrep ERE_PATTERN FILENAME
while IFS= read -r line || [[ $line ]]; do
[[ $line =~ $1 ]] && echo "$line"
done <"$2"
(We iterate over lines of the file given in the second positional parameter, $2, in a pretty standard way, checking for a match with a pattern given as the first parameter inside a conditional expression with the =~ operator, printing all lines that match.)
Invoke it like:
./mygrep command: file
Although much slower than grep, one nice thing about this script is that it supports POSIX ERE (extended regular expressions) by default (you don't need to specify -E like you do in grep), e.g.:
./mygrep 'com.*:' file
./mygrep '^[[:digit:]]{3}' file
# etc
I have a file files.txt with content:
F1.txt
F2.txt
F3.txt
F4.txt
I need to read the file files.txt line by line, and check if it exists in the current directory, if it does, I need to append the date at the end of each of line of files.txt, so that the output should be
F1.txt16032017
F2.txt16032017
F3.txt16032017
F4.txt16032017
I have used following simple shell script.
#!/bin/bash
DT=`date +%d%m%Y`
while IFS=read -r line
do
if [ -f $line]
then
echo "$line$DT" > files.ok
else
echo "$line" > files.notok
fi
done < files.txt
It executes without any error, but does not provide expected output with date append. Can someone tell me if the file existence test is correct
The problem is with your file write operator > which creates a creates a new file on every successful case, you need to have used the >> operator which appends to the existing file.
A much neater approach to your code would be to do
#!/bin/bash
dateToday="$(date +%d%m%Y)"
while IFS= read -r file; do
[ -f "$file" ] && printf "%s\n" "$file$dateToday" >> files.OK || printf "%s\n" "$file" >> files.NOK
done < files.txt
What updates that I have made to improve the script,
Removed the outdated command substitution syntax using backticks `` and used the $(..) for running them.
Double-quoted the variables, lower-cased the local variables.
Fixed the file write operator from > to >>
Used a single line condition making use of the return code of the test ([]) operator. The command after && runs if the condition [ -f "$file" ] is successful and the command after || runs if the condition fails.
I have the following script called test.sh:
echo "file path is : $1"
path=$1
while read -r line
do
num=$($line | tr -cd [:digit:])
echo num
done < $path
exit 0
I am attempting to grab the digit at the start of each line of the file stored as $path. the end result will be to loop over each line, grab the digit and remove it from the file if it is less than 2.
Every time i run this loop i get the error "./test.sh: line 5: : command not found. What part of the while loop am I doing wrong? Or is it something to do with the tr command?
I can spot a few things wrong with your script:
#!/bin/bash
echo "file path is : $1"
path=$1
while read -r line
do
num=$(tr -cd '[:digit:]' <<<"$line") # use here string to "echo" variable to tr
echo "$num" # added quotes and $
done < "$path" # added quotes, changed $dest to $path
In summary:
cmd <<<"$var" (here string) is a bash built-in designed as a replacement for echo "$var" | cmd. I added #!/bin/bash to the top of the script, as I am using this bash-only feature.
I have quoted your variables to prevent problems with word splitting and glob expansion.
I made the assumption that you really meant to use $path on the last line (though I may be wrong).
Finally, there's no need to exit 0 at the end of your script.
So I'm writing a simple backup script that when called will either back up a specific file or all the files in my current directory into my backup directory.
This is my script
#!/bin/bash
#verify if $1 is empty, if so, copy all content to backup directory
if [ -z "$1" ]
then
$files=ls
#Looping through files
for file in $files
do
cp $file ../backup/
done
#else copy files specified
else
$files=ls $1
#Looping through files
for file in $files
do
cp $file ../backup/
done
fi
and the only error I'm getting is:
./backup: line 7: =ls: command not found
I'm not sure why the script won't recognize ls as a command. Any ideas?
to assign a variable, you don't need the dollar sign:
files=foo
to save the output of an command to a var, you need do:
files=$(ls)
or
files=$(ls /path/to/some/dir)
I see two mistakes:
When you initialize variable "files" you should not prepend it with "$" symbol
Command ls should be placed in back quotes (`):
This is a short example:
#!/bin/bash
files=`ls`
for file in $files
do
echo "file: $file"
done
You should try putting the ls into ` marks - better, the back quote. Like this:
files=`ls`
Here a little background. Check this page for more information about quotation marks in the shell environment:
The back quote is not used for quoting characters. That character is
used for command substitution, where the characters between them are
executed by the shell and the results is inserted on that line.
Example:
echo the date is `date`