How do you validate file paths in TCL? - string

Given a string, I'd like to know if the string represents a valid file path. I've looked at [file isfile <string>] and [file isdirectory <string>] but it seems those two return false if the given string isn't pointing to an existing file/directory. I'm merely interested in the validity of the string.

Pretty much every string is a valid file path. Can you give some examples of what you consider invalid file paths?
You can use file pathtype to check if the path is absolute. Also file split and file normalize may be useful, depending on what you really need.

I don't know that there's a particular way to do that.
I suppose you could try:
set fn /home/bll/mytest.txt
set fail true
if { [file exists $fn] } {
set fail false
} else {
try {
set fh [open $fn w]
set fail false
file delete $fn
} on error {err res} {
}
}
Even if the above works, certain characters may cause issues if you use
the filename in an external command line.
I strip the following characters simply to avoid possible command line problems:
* : " ? < > | [:cntrl:]
I have this list as possibly problematical for unix:
* & [] ? ' " < > |
And this list for windows:
* : () & ^ | < > ' "
Also, a trailing dot is illegal for windows directory names.
And slashes or backslashes can cause issues as those are directory separators.

Related

How to compare two parts of a line multiple times in multiple files with specific extension?

NOTE BEFORE READING: The following question is described very precisely and that is the reason for the length of a question. If you want to understand the problem, it's better to read the entire thing. Many thanks for all the answers!
I am working on a bash script (.sh file) which will check certain values in every file of a directory. Bash script will be executed in a pre-commit (pre-commit is not a part of the question).
There is a directory that contains multiple .c files in multiple subdirectories. I want to check a part of two lines which are NOT in every .c file but only in some of them. The structure of a file that contains the useful information is as following:
/*
## SYMBOL = some_symbol1
## A2L_TYPE = PARAMETER
.
.
.
#! DEFAULT = some_value1
## END
*/
some_symbol1 = some_value1
/*
## SYMBOL = some_symbol2
## A2L_TYPE = PARAMETER
.
.
.
#! DEFAULT = some_value2
## END
*/
some_symbol2 = some_value2
This kind of structure is automatically generated by another script.
I want to check if some_value1 (in comment) is equal to some_value1 (in variable).
There are hundreds of these variable in each .c file (not necessarily in each .c file).
The main functionality of a script should be:
Check some_value1 in comment and variable and throw an error if they are not the same. Script has to go through EVERY .c file in a directory (bash is in root) and ALL subdirectories to find previously mentioned structure.
Value of variable can be something as 0.06F, where in comment, there is 0.06 (compare only the numbers)
Value of variable can also be an array: { 0.0F, 0.45F, 0.3F } where in the comment, there is [ 0.0, 0.45, 0.3 ] (without F and difference in braces)
To summarize:
I want to build a check script that compares some_value1 (in comment) and some_value1 (in variable) and throw an error if they don't match
Useful information is not in EVERY .c file but only in some of them (don't know which)
Values after #! DEFAULT is a comment where the value of variable is a number (maybe this is not that important?)
between A2L_TYPE and DEFAULT, there can be desired number of unimportant stuff. (still a comment)
What I tried so far is for loop through every .c file and a nested for loop to read every line in each .c file. What I wanted to implement was a grep command inside for loop to check each line if there is a #! DEFAULT pattern and save it to the variable.
Latest code that I tried:
!/bin/bash
shopt -s globstar
for d in */**/*.c
do
while IFS="" read -r p || [ -n "$p" ]
do
grep -P "#! DEFAULT" $d
done < $d
done
This is currently not working because it gives an error that certain grep targets are directories
If any has any questions, I will try to explain it better.
# search for files with extension ".c"
# execute awk on any matches, using '= ' as field separator
find . -type f -name '*.c' -exec awk -F'=[[:space:]]*' '
# check if first three lines match template
( NR==1 && /^\/\*/ ) ||
( NR==2 && /^## SYMBOL = / ) ||
( NR==3 && /^## A2L_TYPE = PARAMETER/ ) { ok++ }
# template mismatch - skip this file
( NR==4 && ok!=3 ) {
printf "%s : ignored\n", FILENAME
nextfile
}
# store first occurrence of some_value1
# note line number where second occurrence expected
/^#! DEFAULT =/ { v[1]=v1=$2; n=NR+3 }
# test second occurrence
NR==n {
v[2]=v2=$2;
# prune everything except numbers and array delimiters
for (s in v) gsub(/[^0-9.,]/,"",v[s]);
# output result
# match exactly or only number list
printf "%s #(%d,%d) : ", FILENAME,n-3,n
if (v1==v2 || v[1]==v[2])
printf "match (%s)==(%s)\n", v1,v2
else
printf "mismatch (%s)!=(%s)\n", v1,v2
# no need to check rest of this file
# elide to check multiple values per file
nextfile
}
' {} +

^M still at the end of my string even after chop/chomp

I am wanting to pass a string variable in a ssh command. You can see in the code below I ssh to a server then cd to a directory that I pass a variable to. (cd $orig)
The variable is pulled from a file that I read in and put into an array.
I think that is where my error is because there might be unwanted hidden characters after I used the split command to read in from the file.
Here is the error I get:
ksh: /OnSight/jetplan/scripts/release/jscripts^M: not found
Can't open perl script "AddAlias.pl": No such file or directory
/OnSight/users/onsadm
SSHing to densbp53
/OnSight//scripts/release/jscripts
It can't find my script because the CD to the folder fails.
Sometimes the error says that 'end of file' can't be found. Like I'm doing a CD command with a EOF hidden symbol.
And here is the code:
for(my $j=0; $j < $#servName+1; $j++)
{
print "\nSSHing to $servName[$j]\n\n";
my $orig = $scriptfileLoc[$j];
#my $chopped = chop($orig);
chop($orig);
chomp($orig);
print ("\n$orig\n");
$sshstart = `ssh $servName[$j] "cd $orig; pwd; perl AddAlias.pl $aliasName $aliasCommand $addperl $servProfileLoc[$j]"`;
print $sshstart;
}
It outputs the $orig variable and it looks fine after the chop and chomp. (Which I've done both by themselves and still got the same error) So I pass it in my SSH command and it doesnt work.
I have a server file that holds all the server information, and yes it looks repetative I know.
densbp40:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp41:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp42:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp43:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp50:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp51:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp52:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp53:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp60:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp61:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp62:/export/home/.profile:/OnSight/scripts/release/jscripts
tulsbp40:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp41:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp42:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp43:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp50:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp51:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp52:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp53:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
densbcp1:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
densbcp2:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
densmsv1:/OnSight/.profile:/OnSight/scripts/jscripts
denamdp1:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
denamap1:/OnSight/users/profile:/OnSight/scripts/release/jscripts
denamap2:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
denfpev1:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
This script asks the user to choose to send a file to ALL servers or just one.
To remove CR (^M) from the end of lines, use the following regex:
$orig =~ s/\r$//gm;
Anchoring at the line end guarantees that any other carriage return characters are not removed from your input. (You probably don't them there either, but to normalize line endings, it's better to not touch other characters).
g enables global matches (not only the first) and m enables multiline mode, so that $ matches the end of each line in a multiline string, not only the end of the string.
"^M" is carriage return a.k.a "\r". Use regex to remove it:
$orig =~ s/\r//g;

how to combine directory path in perl

I am having a perl script in which i am giving path to directory as input.
Directory has xml files inside it.
In my code i am iterating through all the xml files and creating absolute path for all xml files. Code is working fine.
#!/usr/bin/perl
use File::Spec;
$num_args = $#ARGV + 1;
if ($num_args != 1) {
print "\nUsage: $0 <input directory>\n";
exit;
}
my $dirPath = $ARGV[0];
opendir(DIR, $dirPath);
my #docs = grep(/\.xml$/,readdir(DIR));
foreach my $file (#docs)
{
my $abs_path = join("",$dir,$file);
print "absolute path is $abs_path";
}
Question i have here is,
joining $dirPath and $file with no separator which means that $dirPath must end in a "/". So is there any way or built in function in perl which take cares of this condition and replaces the join method.
All i want is not to worry about the separator "/". Even if script is called with path as "/test/dir_to_process" or "/test/dir_to_process/", i should be able to produce the correct absolute path to all xml files present without worrying about the separator.
Let me know if anyone has any suggestions.
Please take heed of the advice you are given. It is ridiculous to keep asking questions when comments and answers to previous posts are being ignored.
You must always use strict and use warnings at the top of every Perl program you write, and declare every variable using my. It isn't hard to do, and you will be reprimanded if you post code that doesn't have these measures in place.
You use the File::Spec module in your program but never make use of it. It is often easier to use File::Spec::Functions instead, which exports the methods provided by File::Spec so that there is no need to use the object-oriented call style.
catfile will correctly join a file (or directory) name to a path, doing the right thing if path separators are incorrect. This rewrite of your program works fine.
#!/usr/bin/perl
use strict;
use warnings;
use File::Spec::Functions 'catfile';
if (#ARGV != 1) {
print "\nUsage: $0 <input directory>\n";
exit;
}
my ($dir_path) = #ARGV;
my $xml_pattern = catfile($dir_path, '*.xml');
while ( my $xml_file = glob($xml_pattern) ) {
print "Absolute path is $xml_file\n";
}
The answer is in the documentation for File::Spec, e.g., catfile:
$path = File::Spec->catfile( #directories, $filename );
or catpath:
$full_path = File::Spec->catpath( $volume, $directory, $file );
This will add the trailing slash if not there:
$dirPath =~ s!/*$!/!;

incrementing integer value stored as tcl string

I have to implement a feature in which i have a file name and that name contains some integer value in the end and i want to increment that integer value by one. To be more clear, the problem string "filename" is :
asdfasdfasdfa.foo.old001
now i have to check if this file name exists, and if it does i have to name my new file to
asdfasdfasdfa.foo.old002
and so on. I can think of one solution which is to get the last string ".old001" using
file extension string
command and then using regular expression get the last three characters and add '1' to it and then join the file name to this new extension. But i am not sure if it is a good and optimal solution. Any other ideas or help would be great. Thanks.
proc incr_filename {name} {
set digits [regexp -inline {\d+$} $name]
set prefix [string range $name 0 end-[string length $digits]]
set num [scan $digits %d] ;# prevents problems with invalid octal numbers
format "%s%0*d" $prefix [string length $digits] [incr num]
}
puts [incr_filename asdfasdfasdfa.foo.old001]
And to find the non-existant filename:
set filename abcd.ext.old001
while {[file exists $filename]} {
set filename [incr_filename $filename]
}
puts "new filename is $filename"

How to concatenate two strings to build a complete path

I am trying to write a bash script. In this script I want user to enter a path of a directory. Then I want to append some strings at the end of this string and build a path to some subdirectories.
For example assume user enters an string like this:
/home/user1/MyFolder
Now I want to create 2 subdirectories in this directory and copy some files there.
/home/user1/MyFolder/subFold1
/home/user1/MyFolder/subFold2
How can I do this?
The POSIX standard mandates that multiple / are treated as a single / in a file name. Thus
//dir///subdir////file is the same as /dir/subdir/file.
As such concatenating a two strings to build a complete path is a simple as:
full_path="$part1/$part2"
#!/bin/bash
read -p "Enter a directory: " BASEPATH
SUBFOLD1=${BASEPATH%%/}/subFold1
SUBFOLD2=${BASEPATH%%/}/subFold2
echo "I will create $SUBFOLD1 and $SUBFOLD2"
# mkdir -p $SUBFOLD1
# mkdir -p $SUBFOLD2
And if you want to use readline so you get completion and all that, add a -e to the call to read:
read -e -p "Enter a directory: " BASEPATH
Won't simply concatenating the part of your path accomplish what you want?
$ base="/home/user1/MyFolder/"
$ subdir="subFold1"
$ new_path=$base$subdir
$ echo $new_path
/home/user1/MyFolder/subFold1
You can then create the folders/directories as needed.
One convention is to end directory paths with / (e.g. /home/) because paths starting with a / could be confused with the root directory. If a double slash (//) is used in a path, it is also still correct. But, if no slash is used on either variable, it would be incorrect (e.g. /home/user1/MyFoldersubFold1).
The following script catenates several (relative/absolute) paths (BASEPATH) with a relative path (SUBDIR):
shopt -s extglob
SUBDIR="subdir"
for BASEPATH in '' / base base/ base// /base /base/ /base//; do
echo "BASEPATH = \"$BASEPATH\" --> ${BASEPATH%%+(/)}${BASEPATH:+/}$SUBDIR"
done
The output of which is:
BASEPATH = "" --> subdir
BASEPATH = "/" --> /subdir
BASEPATH = "base" --> base/subdir
BASEPATH = "base/" --> base/subdir
BASEPATH = "base//" --> base/subdir
BASEPATH = "/base" --> /base/subdir
BASEPATH = "/base/" --> /base/subdir
BASEPATH = "/base//" --> /base/subdir
The shopt -s extglob is only necessary to allow BASEPATH to end on multiple slashes (which is probably nonsense). Without extended globing you can just use:
echo ${BASEPATH%%/}${BASEPATH:+/}$SUBDIR
which would result in the less neat but still working:
BASEPATH = "" --> subdir
BASEPATH = "/" --> /subdir
BASEPATH = "base" --> base/subdir
BASEPATH = "base/" --> base/subdir
BASEPATH = "base//" --> base//subdir
BASEPATH = "/base" --> /base/subdir
BASEPATH = "/base/" --> /base/subdir
BASEPATH = "/base//" --> /base//subdir
I was working around with my shell script which need to do some path joining stuff like you do.
The thing is, both path like
/data/foo/bar
/data/foo/bar/
are valid.
If I want to append a file to this path like
/data/foo/bar/myfile
there was no native method (like os.path.join() in python) in shell to handle such situation.
But I did found a trick
For example , the base path was store in a shell variable
BASE=~/mydir
and the last file name you wanna join was
FILE=myfile
Then you can assign your new path like this
NEW_PATH=$(realpath ${BASE})/FILE
and then you`ll get
$ echo $NEW_PATH
/path/to/your/home/mydir/myfile
the reason is quiet simple, the "realpath" command would always trim the terminating slash for you if necessary
This should works for empty dir (You may need to check if the second string starts with / which should be treat as an absolute path?):
#!/bin/bash
join_path() {
echo "${1:+$1/}$2" | sed 's#//#/#g'
}
join_path "" a.bin
join_path "/data" a.bin
join_path "/data/" a.bin
Output:
a.bin
/data/a.bin
/data/a.bin
Reference: Shell Parameter Expansion
I ended up concating all arguments ($*) with '/' (IFS=/ - Internal Field Separator) as separator and then removing all repeating '/' (tr -s /).
My function looks like this:
join_paths() {
(IFS=/; echo "'$*'" | tr -s /)
}
#Dunes' basic solution, but accounting for empty strings:
[ -z "$rootpath" ] && rootpath="."
fullpath="${rootpath}/${subdir}"

Resources