Related
I want to grab the first file in a directory, without touching/grabbing all the other files. The filename is unknown.
One very short way could be this, using glob:
$file = array_slice(glob('/directory/*.jpg'), 0, 1);
But if there are a lot of files in that directory, there will be some overhead.
Other ways are answers to this question - but all involve a loop and are also longer then the glob example:
PHP: How can I grab a single file from a directory without scanning entire directory?
Is there a very short and efficient way to solve this?
Probably not totally efficient, but if you only want the FIRST jpg that appears, then
$dh = opendir('directory/');
while($filename = readdir($dh)) {
if (substr($filename, -4) == '.jpg')) {
break;
}
}
Well this is not totally a one-liner, but it is a way to go I believe:
$result = null;
foreach(new FilesystemIterator('directory/') as $file)
{
if($file->isFile() && $file->getExtension() == 'jpg') {
$result = $file->getPathname();
break;
}
}
but why don't you wrap it in a function and use it like get_first_file('directory/') ? It will be a nice and short!
This function will get the first filename of any type.
function get_first_filename ($dir) {
$d = dir($dir);
while ($f = $d->read()){
if (is_file($dir . '/' . $f)) {
$d->close();
return $f;
}
}
}
I have a function to check if a file exists via jQuery which makes a call to a PHP script which I'll use when changing certain images at the click of a button on my index page.
jQuery function:
function fileExists(path){
$.getJSON("/ajax/fileExists.php",{ path: path },
function (data){
return data.path;
});
}
fileExists.php:
$path=$_SERVER['DOCUMENT_ROOT'].'/packs'.$_GET['path'];
if(file_exists($path)){
echo json_encode(TRUE);
}else{
echo json_encode(FALSE);
}
I'm worried about people using this script to list the contents of my server or files which I may not want them to know about so I've used DOCUMENT_ROOT and /packs to try to limit calls to that directory but I think people can simply use ../ within the supplied path to check alternatives.
What is the best way to make this safe, ideally limit it to /packs, and are there any other concerns I should worry about?
Edit: an example call in javascript/jQuery:
if( fileExists('/index.php') ){
alert('Exists');
}else{
alert('Doesn\'t exist');
}
This is how I've handled it in the past:
$path = realpath($_SERVER['DOCUMENT_ROOT'].'/packs'.$_GET['path']);
if (strpos($path, $_SERVER['DOCUMENT_ROOT']) !== 0) {
//It's looking to a path that is outside the document root
}
You can remove any path-transversing from your filename:
$path_arr = explode("/", $_GET['path']);
$path = $path_arr[count($path_arr - 1)];
Such a practice is moderately secure and fast (O(1) complexity) but is not really the best as you have to watch out for encoding, character replacement and all like stuff.
But the overall best practice (though less faster depending on your directory size, let's say O(n) complexity) would be to use readdir() to get a list of all the files in your /packs directory then see if the supplied filename is present:
$handle = opendir($path=$_SERVER['DOCUMENT_ROOT'].'/packs');
while (false !== ($entry = readdir($handle))) {
if ($entry === $_GET['path']) {
echo json_encode(TRUE);
return;
}
}
echo json_encode(FALSE);
Apparently, realpath is very buggy. In PHP 5.3.1, it causes random crashes.
In 5.3.0 and less, realpath randomly fails and returns false (for the same string of course), plus it always fails on realpath-ing the same string twice/more (and of course, it works the first time).
Also, it is so buggy in earlier PHP versions, that it is completely unusable. Well...it already is, since it's not consistent.
Anyhow, what options do I have? Maybe rewrite it by myself? Is this advisable?
Thanks to Sven Arduwie's code (pointed out by Pekka) and some modification, I've built a (hopefully) better implementation:
/**
* This function is to replace PHP's extremely buggy realpath().
* #param string The original path, can be relative etc.
* #return string The resolved path, it might not exist.
*/
function truepath($path){
// whether $path is unix or not
$unipath=strlen($path)==0 || $path{0}!='/';
// attempts to detect if path is relative in which case, add cwd
if(strpos($path,':')===false && $unipath)
$path=getcwd().DIRECTORY_SEPARATOR.$path;
// resolve path parts (single dot, double dot and double delimiters)
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutes = array();
foreach ($parts as $part) {
if ('.' == $part) continue;
if ('..' == $part) {
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}
$path=implode(DIRECTORY_SEPARATOR, $absolutes);
// resolve any symlinks
if(file_exists($path) && linkinfo($path)>0)$path=readlink($path);
// put initial separator that could have been lost
$path=!$unipath ? '/'.$path : $path;
return $path;
}
NB: Unlike PHP's realpath, this function does not return false on error; it returns a path which is as far as it could to resolving these quirks.
Note 2: Apparently some people can't read properly. Truepath() does not work on network resources including UNC and URLs. It works for the local file system only.
here is the modified code that supports UNC paths as well
static public function truepath($path)
{
// whether $path is unix or not
$unipath = strlen($path)==0 || $path{0}!='/';
$unc = substr($path,0,2)=='\\\\'?true:false;
// attempts to detect if path is relative in which case, add cwd
if(strpos($path,':') === false && $unipath && !$unc){
$path=getcwd().DIRECTORY_SEPARATOR.$path;
if($path{0}=='/'){
$unipath = false;
}
}
// resolve path parts (single dot, double dot and double delimiters)
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutes = array();
foreach ($parts as $part) {
if ('.' == $part){
continue;
}
if ('..' == $part) {
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}
$path = implode(DIRECTORY_SEPARATOR, $absolutes);
// resolve any symlinks
if( function_exists('readlink') && file_exists($path) && linkinfo($path)>0 ){
$path = readlink($path);
}
// put initial separator that could have been lost
$path = !$unipath ? '/'.$path : $path;
$path = $unc ? '\\\\'.$path : $path;
return $path;
}
I know this is an old thread, but it is really helpful.
I meet a weird Phar::interceptFileFuncs issue when I implemented relative path in phpctags, the realpath() is really really buggy inside phar.
Thanks this thread give me some lights, here comes with my implementation based on christian's implemenation from this thread and this comments.
Hope it works for you.
function relativePath($from, $to)
{
$fromPath = absolutePath($from);
$toPath = absolutePath($to);
$fromPathParts = explode(DIRECTORY_SEPARATOR, rtrim($fromPath, DIRECTORY_SEPARATOR));
$toPathParts = explode(DIRECTORY_SEPARATOR, rtrim($toPath, DIRECTORY_SEPARATOR));
while(count($fromPathParts) && count($toPathParts) && ($fromPathParts[0] == $toPathParts[0]))
{
array_shift($fromPathParts);
array_shift($toPathParts);
}
return str_pad("", count($fromPathParts)*3, '..'.DIRECTORY_SEPARATOR).implode(DIRECTORY_SEPARATOR, $toPathParts);
}
function absolutePath($path)
{
$isEmptyPath = (strlen($path) == 0);
$isRelativePath = ($path{0} != '/');
$isWindowsPath = !(strpos($path, ':') === false);
if (($isEmptyPath || $isRelativePath) && !$isWindowsPath)
$path= getcwd().DIRECTORY_SEPARATOR.$path;
// resolve path parts (single dot, double dot and double delimiters)
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
$pathParts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutePathParts = array();
foreach ($pathParts as $part) {
if ($part == '.')
continue;
if ($part == '..') {
array_pop($absolutePathParts);
} else {
$absolutePathParts[] = $part;
}
}
$path = implode(DIRECTORY_SEPARATOR, $absolutePathParts);
// resolve any symlinks
if (file_exists($path) && linkinfo($path)>0)
$path = readlink($path);
// put initial separator that could have been lost
$path= (!$isWindowsPath ? '/'.$path : $path);
return $path;
}
For those Zend users out there, THIS answer may help you, as it did me:
$path = APPLICATION_PATH . "/../directory";
$realpath = new Zend_Filter_RealPath(new Zend_Config(array('exists' => false)));
$realpath = $realpath->filter($path);
I have never heard of such massive problems with realpath() (I always thought that it just interfaces some underlying OS functionality - would be interested in some links), but the User Contributed Notes to the manual page have a number of alternative implementations. Here is one that looks okay.
Of course, it's not guaranteed these implementations take care of all cross-platform quirks and issues, so you'd have to do thorough testing to see whether it suits your needs.
As far as I can see though, none of them returns a canonicalized path, they only resolve relative paths. If you need that, I'm not sure whether you can get around realpath() (except perhaps executing a (system-dependent) console command that gives you the full path.)
On Windows 7, the code works fine. On Linux, there is a problem in that the path generated starts with (in my case) home/xxx when it should start with /home/xxx ... ie the initial /, indicating the root folder, is missing.
The problem is not so much with this function, but with what getcwd returns in Linux.
I'm looking for a small function that allows me to remove the extension from a filename.
I've found many examples by googling, but they are bad, because they just remove part of the string with "." . They use dot for limiter and just cut string.
Look at these scripts,
$from = preg_replace('/\.[^.]+$/','',$from);
or
$from=substr($from, 0, (strlen ($from)) - (strlen (strrchr($filename,'.'))));
When we add the string like this:
This.is example of somestring
It will return only "This"...
The extension can have 3 or 4 characters, so we have to check if dot is on 4 or 5 position, and then remove it.
How can it be done?
http://php.net/manual/en/function.pathinfo.php
pathinfo — Returns information about a file path
$filename = pathinfo('filename.md.txt', PATHINFO_FILENAME); // returns 'filename.md'
Try this one:
$withoutExt = preg_replace('/\\.[^.\\s]{3,4}$/', '', $filename);
So, this matches a dot followed by three or four characters which are not a dot or a space. The "3 or 4" rule should probably be relaxed, since there are plenty of file extensions which are shorter or longer.
From the manual, pathinfo:
<?php
$path_parts = pathinfo('/www/htdocs/index.html');
echo $path_parts['dirname'], "\n";
echo $path_parts['basename'], "\n";
echo $path_parts['extension'], "\n";
echo $path_parts['filename'], "\n"; // Since PHP 5.2.0
?>
It doesn't have to be a complete path to operate properly. It will just as happily parse file.jpg as /path/to/my/file.jpg.
Use PHP basename()
(PHP 4, PHP 5)
var_dump(basename('test.php', '.php'));
Outputs: string(4) "test"
This is a rather easy solution and will work no matter how long the extension or how many dots or other characters are in the string.
$filename = "abc.def.jpg";
$newFileName = substr($filename, 0 , (strrpos($filename, ".")));
//$newFileName will now be abc.def
Basically this just looks for the last occurrence of . and then uses substring to retrieve all the characters up to that point.
It's similar to one of your googled examples but simpler, faster and easier than regular expressions and the other examples. Well imo anyway. Hope it helps someone.
Recommend use: pathinfo with PATHINFO_FILENAME
$filename = 'abc_123_filename.html';
$without_extension = pathinfo($filename, PATHINFO_FILENAME);
You could use what PHP has built in to assist...
$withoutExt = pathinfo($path, PATHINFO_DIRNAME) . '/' . pathinfo($path, PATHINFO_FILENAME);
Though if you are only dealing with a filename (.somefile.jpg), you will get...
./somefile
See it on CodePad.org
Or use a regex...
$withoutExt = preg_replace('/\.' . preg_quote(pathinfo($path, PATHINFO_EXTENSION), '/') . '$/', '', $path);
See it on CodePad.org
If you don't have a path, but just a filename, this will work and be much terser...
$withoutExt = pathinfo($path, PATHINFO_FILENAME);
See it on CodePad.org
Of course, these both just look for the last period (.).
The following code works well for me, and it's pretty short. It just breaks the file up into an array delimited by dots, deletes the last element (which is hypothetically the extension), and reforms the array with the dots again.
$filebroken = explode( '.', $filename);
$extension = array_pop($filebroken);
$fileTypeless = implode('.', $filebroken);
I found many examples on the Google but there are bad because just remove part of string with "."
Actually that is absolutely the correct thing to do. Go ahead and use that.
The file extension is everything after the last dot, and there is no requirement for a file extension to be any particular number of characters. Even talking only about Windows, it already comes with file extensions that don't fit 3-4 characters, such as eg. .manifest.
There are a few ways to do it, but i think one of the quicker ways is the following
// $filename has the file name you have under the picture
$temp = explode( '.', $filename );
$ext = array_pop( $temp );
$name = implode( '.', $temp );
Another solution is this. I havent tested it, but it looks like it should work for multiple periods in a filename
$name = substr($filename, 0, (strlen ($filename)) - (strlen (strrchr($filename,'.'))));
Also:
$info = pathinfo( $filename );
$name = $info['filename'];
$ext = $info['extension'];
// Or in PHP 5.4, i believe this should work
$name = pathinfo( $filename )[ 'filename' ];
In all of these, $name contains the filename without the extension
$image_name = "this-is.file.name.jpg";
$last_dot_index = strrpos($image_name, ".");
$without_extention = substr($image_name, 0, $last_dot_index);
Output:
this-is.file.name
As others mention, the idea of limiting extension to a certain number of characters is invalid. Going with the idea of array_pop, thinking of a delimited string as an array, this function has been useful to me...
function string_pop($string, $delimiter){
$a = explode($delimiter, $string);
array_pop($a);
return implode($delimiter, $a);
}
Usage:
$filename = "pic.of.my.house.jpeg";
$name = string_pop($filename, '.');
echo $name;
Outputs:
pic.of.my.house (note it leaves valid, non-extension "." characters alone)
In action:
http://sandbox.onlinephpfunctions.com/code/5d12a96ea548f696bd097e2986b22de7628314a0
This works when there is multiple parts to an extension and is both short and efficient:
function removeExt($path)
{
$basename = basename($path);
return strpos($basename, '.') === false ? $path : substr($path, 0, - strlen($basename) + strlen(explode('.', $basename)[0]));
}
echo removeExt('https://example.com/file.php');
// https://example.com/file
echo removeExt('https://example.com/file.tar.gz');
// https://example.com/file
echo removeExt('file.tar.gz');
// file
echo removeExt('file');
// file
You can set the length of the regular expression pattern by using the {x,y} operator. {3,4} would match if the preceeding pattern occurs 3 or 4 times.
But I don't think you really need it. What will you do with a file named "This.is"?
Landed on this page for looking for the fastest way to remove the extension from a number file names from a glob() result.
So I did some very rudimentary benchmark tests and found this was the quickest method. It was less than half the time of preg_replace():
$result = substr($fileName,0,-4);
Now I know that all of the files in my glob() have a .zip extension, so I could do this.
If the file extension is unknown with an unknown length, the following method will work and is still about 20% faster that preg_replace(). That is, so long as there is an extension.
$result = substr($fileName,0,strrpos($fileName,'.'));
The basic benchmark test code and the results:
$start = microtime(true);
$loop = 10000000;
$fileName = 'a.LONG-filename_forTest.zip';
$result;
// 1.82sec preg_replace() unknown ext
//do {
// $result = preg_replace('/\\.[^.\\s]{3,4}$/','',$fileName);
//} while(--$loop);
// 1.7sec preg_replace() known ext
//do {
// $result = preg_replace('/.zip$/','',$fileName);
//} while(--$loop);
// 4.57sec! - pathinfo
//do {
// $result = pathinfo($fileName,PATHINFO_FILENAME);
//} while(--$loop);
// 2.43sec explode and implode
//do {
// $result = implode('.',explode('.',$fileName,-1));
//} while(--$loop);
// 3.74sec basename, known ext
//do {
// $result = basename($fileName,'.zip');
//} while(--$loop);
// 1.45sec strpos unknown ext
//do {
// $result = substr($fileName,0,strrpos($fileName,'.'));
//} while(--$loop);
// 0.73sec strpos - known ext length
do {
$result = substr($fileName,0,-4);
} while(--$loop);
var_dump($fileName);
var_dump($result);
echo 'Time:['.(microtime(true) - $start).']';
exit;
Use this:
strstr('filename.ext','.',true);
//result filename
Try to use this one. it will surely remove the file extension.
$filename = "image.jpg";
$e = explode(".", $filename);
foreach($e as $key=>$d)
{
if($d!=end($e)
{
$new_d[]=$d;
}
}
echo implode("-",$new_t); // result would be just the 'image'
EDIT:
The smartest approach IMHO, it removes the last point and following text from a filename (aka the extension):
$name = basename($filename, '.' . end(explode('.', $filename)));
Cheers ;)
I have a function that detects all files started by a string and it returns an array filled with the correspondent files, but it is starting to get slow, because I have arround 20000 files in a particular directory.
I need to optimize this function, but I just can't see how. This is the function:
function DetectPrefix ($filePath, $prefix)
{
$dh = opendir($filePath);
while (false !== ($filename = readdir($dh)))
{
$posIni = strpos( $filename, $prefix);
if ($posIni===0):
$files[] = $filename;
endif;
}
if (count($files)>0){
return $files;
} else {
return null;
}
}
What more can I do?
Thanks
http://php.net/glob
$files = glob('/file/path/prefix*');
Wikipedia breaks uploads up by the first couple letters of their filenames, so excelfile.xls would go in a directory like /uploads/e/x while textfile.txt would go in /uploads/t/e.
Not only does this reduce the number of files glob (or any other approach) has to sort through, but it avoids the maximum files in a directory issue others have mentioned.
You could use scandir() to list the files in the directory, instead of iterating through them one-by-one using readdir(). scandir() returns an array of the files.
However, it'd be better if you could change your file system organization - do you really need to store 20000+ files in a single directory?
As the other answers mention, I'd look at glob(), scandir(), and/or the DirectoryIterator class, there is no need to recreate the wheel.
However watch out! check your operating system, but there may be a limit on the maximum number of files in a single directory. If this is the case and you just keep adding files in the same directory you will have some downtime, and some problems, when you reach the limit. This error will probably appear as a permissions or write failure and not an obvious "you can't write more files in a single directory" message.
I'm not sure but probably DirectoryIterator is a bit faster. Also add caching so that list gets generated only when files are added or deleted.
You just need to compare the first length of prefix characters. So try this:
function DetectPrefix($filePath, $prefix) {
$dh = opendir($filePath);
$len = strlen($prefix);
$files = array();
while (false !== ($filename = readdir($dh))) {
if (substr($filename, 0, $len) === $prefix) {
$files[] = $filename;
}
}
if (count($files)) {
return $files;
} else {
return null;
}
}