I am writing my first BASH script to automate configuration of my (Laravel) web projects.
I have some config files (app/config/local/database.php,app/config/app.php) with PHP arrays that I need to access and modify. For example ...
'providers' => array(
/** Append new service provider value, if it does not exist already */
'Illuminate\Foundation\Providers\ArtisanServiceProvider',
'Illuminate\Auth\AuthServiceProvider',
// ...
)
... or ...
'mysql' => array(
/** Replace value under key "database" to "test_db" */
'database' => 'homestead',
'username' => 'homestead',
)
So far I was using sed expressions like this:
$LV_DB_NAME="test_db"
$LV_DB_FILE="app/config/local/database.php"
gsed -i "s/'database' .*/'database' => '$LV_DB_NAME',/g" $LV_FILE_DB_CONFIG
This feels a little messy to me, especially in the case of example 1.
What would be awesome
Is there any way to get PHP arrays to BASH arrays and work with like you would in PHP?
Example 1
if (!in_array($new_provider, $providers)) {
$providers[] = $new_provider;
}
Example 2
$config['mysql']['database'] = $database_name
Update: What would also be awesome
If there is any other common way how to modify PHP arrays using terminal, I would be glad if you point me to it! I'm sure I'm not the only one who needs to modify PHP configuration arrays using terminal.
mTorres was actually right. If your machine has php cli installed (which is probable), You can easily jump into PHP from your bash scripts. There are multiple ways to do so, I finally settled with this:
print_s "Putting data to file \"$PATH\" ... "
export PATH=$PATH
export DATA_JSON=$DATA_JSON
/usr/bin/php << 'EOF'
<?php
$path = getenv("PATH");
$data = getenv("DATA_JSON");
$data = json_decode($data);
$config = (file_exists($path) && is_array($config_data = require($path))) ? $config_data : array();
foreach ($data as $k => $v) {
$config[$k] = $v;
}
file_put_contents($path, "<?php \n \n return ".var_export($config,true).";");
?>
EOF
}
There are some gotchas with passing associative arrays in BASH, check my other question: Pass BASH associative arrays to PHP script
Related
Everyone, hello!
I'm currently trying to write to an .ini file from PHP, and I'm using Teoman Soygul's answer and code from here: How to read and write to an ini file with PHP
This works out great, although, when I save the data to it, it shows up strange in my .ini:
[Server] = ""
p_ip = "192.168.10.100"
p_port = 80
p_password = 1234
[Variable] = ""
string1_find = "Caution"
Most notably it also seems to see attempt to give the categories Server and Variable an empty value. Also, sometimes it saves the variable between consistency and sometimes not. How come there is no consistency here?
The code I'm using to find/post in PHP is this:
...
$a=array("[Server]"=>'',"p_ip"=>$_POST['pip'],"p_port"=>$_POST['pport'], "p_password"=>$_POST['pass'],
"[Variable]"=>'',"string1_find"=>$_POST['string1_find'],
...
If anyone could point me into the right direction, that would really be appreciated. Thank you!
You are not using right, you should be passing a multidimentional array instead:
$data = array(
'Server' => array(
'p_ip' => '192.168.10.100',
'p_port' => 80,
'p_password' => 1234,
),
'Variable' => array(
'string1_find' => 'Caution'
)
);
//now call the ini function from Soygul's answer
write_php_ini($data, 'file.ini');
Here is my output:
[Server]
p_ip = "192.168.10.100"
p_port = 80
p_password = 1234
[Variable]
string1_find = "Caution"
Notice that you need to create an extra array per new section and then you can start listing your custom definitions.
Is it possible to pass BASH associative arrays as argv to PHP scripts?
I have a bash script, that collects some variables to a bash associative array like this. After that, I need to send it to PHP script:
typeset -A DATA
DATA[foo]=$(some_bash_function "param1" "param2")
DATA[bar]=$(some_other_bash_function)
php script.php --data ${DATA[#]}
From PHP script, i need to access the array in following manner:
<?php
$vars = getopt("",array(
"data:"
));
$data = $vars['data'];
foreach ($data as $k=>$v) {
echo "$k is $v";
}
?>
What I've tried
Weird syntax around the --data parameter follows advice from a great post about bash arrays from Norbert Kéri how to force passed parameter as an array:
You have no way of signaling to the function that you are passing an array. You get N positional parameters, with no information about the datatypes of each.
However this sollution still does not work for associative arrays - only values are passed to the function. Norbert Kéri made a follow up article about that, however its eval based solution does not work for me, as I need to pass the actual array as a parameter.
Is the thing I'm trying to achieve impossible or is there some way? Thank you!
Update: What I am trying to accomplish
I have a few PHP configuration files of following structure:
<?php
return array(
'option1' => 'foo',
'option2' => 'bar'
)
My bash script collects data from user input (through bash read function) and stores them into bash associative array. This array should be later passed as an argument to PHP script.
php script.php --file "config/config.php" --data $BASH_ASSOC_ARRAY
So instead of complicated seds functions etc. I can do simple:
<?php
$bash_input = getopt('',array('file:,data:'));
$data = $bash_input['data'];
$config = require($config_file);
$config['option1'] = $data['option1'];
$config['option2'] = $data['option2'];
// or
foreach ($data as $k=>$v) {
$config[$k] = $v;
}
// print to config file
file_put_contents($file, "<?php \n \n return ".var_export($config,true).";");
?>
This is used for configuring Laravel config files
Different Approach to #will's
Your bash script:
typeset -A DATA
foo=$(some_bash_function "param1" "param2")
bar=$(some_other_bash_function)
php script.php "{'data': '$foo', 'data2': '$bar'}"
PHP Script
<?php
$vars = json_decode($argv[1]);
$data = $vars['data'];
foreach ($data as $k=>$v) {
echo "$k is $v";
}
?>
EDIT (better approach) Credit to #will
typeset -A DATA
DATA[foo]=$(some_bash_function "param1" "param2")
DATA[bar]=$(some_other_bash_function)
php script.php echo -n "{"; for key in ${!DATA[#]}; do echo - "'$key'":"'${DATA[$key]}'", | sed 's/ /,/g' ; done; echo -n "}"
this does what you want (i think) all in one bash script. You can obviously move the php file out though.
declare -A assoc_array=([key1]=value1 [key2]=value2 [key3]=value3 [key4]=value4)
#These don't come out necesarily ordered
echo ${assoc_array[#]} #echos values
echo ${!assoc_array[#]} #echos keys
echo "" > tmp
for key in ${!assoc_array[#]}
do
echo $key:${assoc_array[$key]} >> tmp # Use some delimeter here to split the keys from the values
done
cat > file.php << EOF
<?php
\$fileArray = explode("\n", file_get_contents("tmp"));
\$data = array();
foreach(\$fileArray as \$line){
\$entry = explode(":", \$line);
\$data[\$entry[0]] = \$entry[1];
}
var_dump(\$data);
?>
EOF
php file.php
the escaping is necessary in the cat block annoyingly.
I'll excuse myself behorehand because i'm quite sure there'll be some obvious solution or an answer already out there, but after quite some extensive search I just cant find it.
I'm developing a simplified web server as a project in .net. What i want to do is calling the php.exe for each HTML request to execute any php code within the file, and then return the result to my server where it will be served to the client.
This was quite simple without passing GET/POST parameters, but I can't find the way to make this work.
Right now i have this as my function to write the call the php via command line
Public Shared Function phpparse(ByVal requesttype As String, ByVal argnames() As String, ByVal argvals() As String)
Dim proc As Process = New Process
proc.StartInfo.FileName = "php\php.exe"
Dim B As New System.Text.StringBuilder
B.Append("-B ""$_")
B.Append(requesttype & " = array(")
For i As Integer = 0 To argnames.Length - 1
B.Append("'" & argnames(i) & "' => '" & argvals(i) & "', ")
Next
B.Remove(B.Length - 2, 2)
B.Append(");"" -F script.php")
InputBox("", "", B.ToString)
proc.StartInfo.Arguments = B.ToString
proc.StartInfo.UseShellExecute = False
proc.StartInfo.RedirectStandardOutput = True
proc.StartInfo.CreateNoWindow = True
proc.Start()
Return proc.StandardOutput.ReadToEnd
End Function
which returns something like this:
-B "$_GET = array('name' => 'John', 'email' => 'john.doe#no.com');" -F script.php
and should be calling this test php script:
<html>
<body>
Welcome <?php echo $_GET["name"]; ?><br>
Your email address is: <?php echo $_GET["email"]; ?>
</body>
</html>
but it gets stuck trying to read the answer
With php you could read the args like this:
<?php
if (isset($argv)) {
echo $argv[1]." ".$argv[2];
}
?>
this will return
John john.doe#no.com
I don't really get it how do you send the command line params.
You should just run the php like
php.exe John john.doe#no.com
I've found a work around the issue. I'm still looking for a proper solution but I can work with this for now
The parameters of the call to the PHP CLI should be
-r "$arrayname = array('arraypar1' => 'arrayval1', 'arraypar2' => 'arrayval2', ); require_once('C:\fullroutetofile\script.php');"
It seems that you can't call -B and -f at the same time, but require_once lets you bypass that.
I am primarily a PHP developer and have limited experience with Perl.
I was tasked with writing a queue script in Perl which checks against a database, and that is all working out great.
The problem I have is that in the Perl script I need to include a database hostname and password.
Right now I have them hard coded, which works fine, but my PHP application uses a global PHP array which holds the database hostname and password.I'd like to be able to use this PHP array in my Perl script.
Here is my PHP array
<?php
return array(
'database' => array(
'master' => array(
'hostname' => 'fd35:4776:6804:2:a::1',
'password' => 'password'
),
'slave' => array(
'hostname' => 'fd35:4776:6804:2:2::2',
'password' => 'password',
'profile' => true
)
)
);
I've tried searching with Google and have read many random posts on line, but I have yet been able to come up with a solution.
Does anyone have any ideas which I could try? If I'm missing any additional input, let me know and I can provide it.
Edit
Hopefully I worded this properly. How would I go about including this PHP array file so that I can manipulate it with Perl?
Alternative solutions are welcome too!
You've discovered one of the many reasons why code makes for bad config files. You should move the information to an actual config file, and access that file from both that .php file and from Perl.
JSON would make a decent file format here.
{
"database": {
"master": {
"hostname": "fd35:4776:6804:2:a::1",
"password": "password"
},
"slave": {
"hostname": "fd35:4776:6804:2:2::2",
"password": "password",
"profile": true
}
}
}
The Perl code would be
use JSON::XS qw( decode_json );
open (my $fh, '<:raw', $config_path)
or die("Can't open config file $config_path: $!\n");
my $file; { local $/; $file = <$fh>; }
my $config = decode_json($file);
On the PHP side, just replace the contents of the file you showed in your post with code to read the config file. I don't know PHP, but it should be quite simple. A quick search shows it might be
return json_decode(file_get_contents($config_path));
It would be simple to provide a short PHP program that dumps the array to a file in JSON format. That file can then be read from Perl using the JSON module.
This is all that is necessary.
<?php
$array = include 'array.php';
$fh = fopen('array.json', 'w');
fwrite($fh, json_encode($array));
fclose($fh);
?>
The resultant JSON file can then be read in a Perl program, like so:
use strict;
use warnings;
use JSON 'from_json';
my $data = do {
open my $fh, '<', 'array.json' or die $!;
local $/;
from_json(<$fh>);
};
use Data::Dump;
dd $data;
output
{
database => {
master => { hostname => "fd35:4776:6804:2:a::1", password => "password" },
slave => {
hostname => "fd35:4776:6804:2:2::2",
password => "password",
profile => bless(do{\(my $o = 1)}, "JSON::XS::Boolean"),
},
},
}
There is PHP::Include, which uses a source filter to let you have PHP blocks in your Perl code to declare variables. It also has a read_file() function that applies such a filter to a single PHP file.
But it seems to expect that your PHP has assignments (e.g. $config = array('database' => array(...) and changes those to Perl variable declarations.
In a few minutes of playing with it, I couldn't get it to do anything useful with your PHP code that uses return.
If you want a more "native Perl" solution, you can pretty much* just search and replace all your "array(" and their matching ")" to "{" and "}". That'll give you a perl datastructure called a "hash of hashes" (note: Unlike PHP, Perl refers to arrays with integer indicies as arrays (and uses the # sigil to denote variables containing them), but refers to array-like things with string indicies as "hashes" (and uses the % sigil to denote variables containing them)). The Perl keywords/concepts you probably want to read up on are:
Perl Data Structures: http://perldoc.perl.org/perldsc.html
and specifically the Hash Of Hashes section: http://perldoc.perl.org/perldsc.html#HASHES-OF-HASHES
and if you dont understand what $hashref = \%hash and %hash{key} and $hashref->{key} mean in Perl, you'd want to read http://perldoc.perl.org/perlref.html
Example code (note how similar the getConfig subroutine is to your PHP code):
#!/usr/bin/perl
use strict;
use warnings;
my $config=getConfig();
print "Database master host = " . $config->{database}{master}{hostname};
print "\n";
print "Database master password = " . $config->{database}{master}{password};
print "\n";
print "Database slave profile = " . $config->{database}{slave}{profile};
print "\n";
sub getConfig{
return {
'database' => {
'master' => {
'hostname' => 'fd35:4776:6804:2:a::1',
'password' => 'password'
},
'slave' => {
'hostname' => 'fd35:4776:6804:2:2::2',
'password' => 'password',
'profile' => 'true'
}
}
};
}
I said "pretty much", because your sample data used the bare word 'true' for the slave->profile value - that's a syntax error in Perl - you can change it to a bare 1, or quote the value as "true" to make it work. In Perl, the digit zero, the string "0" or the empty/nul string "" all evaluate to "false" in a boolean context, anything else evaluates to "true". Take care if you choose to automate PHP to Perl translation, there may be other PHP-isms which could catch you out like that.
So much good information here and it helped me out quite a bit to come up with a working solution.
Here is the perl script I've got working:
#!/usr/bin/perl
use PHP::Include;
include_php_vars( 'config.local.php' );
my $test = \%config;
print $test->{'database'}->{'master'}->{'hostname'};
I also took the PHP array and changed it so that it no longer return array() but $config = array() and then return $config;
This did the trick for me. Thank you!
I want to build a php based site that (automate) some commands on my Ubuntu Server
first thing I did was going to the file (sudoers) and add the user www-data so I can execute php commands with root privileges!
# running the web apps with root power!!!
www-data ALL=(ALL) NOPASSWD: ALL
then my PHP code was
<?php
$command = "cat /etc/passwd | cut -d\":\" -f1";
echo 'running the command: <b>'.$command."</b><br />";
echo exec($command);
?>
it returns only one user (the last user) !!! how to make it return all users?
thank you
From the PHP manual on exec:
Return Values
The last line from the result of the
command. If you need to execute a
command and have all the data from the
command passed directly back without
any interference, use the passthru()
function.
To get the output of the executed command, be sure to set and use the
output parameter.
So you have to do something similar to this:
<?php
$output = array();
$command = "cat /etc/passwd | cut -d\":\" -f1";
echo 'running the command: <b>'.$command."</b><br />";
exec($command, &$output);
echo implode("<br />\n", $output);
?>
As #benjamin explains, no need to be root or sudo, no need for SUID.
Just pure PHP. I used the field names from posix_getpwnam.
function getUsers() {
$result = [];
/** #see http://php.net/manual/en/function.posix-getpwnam.php */
$keys = ['name', 'passwd', 'uid', 'gid', 'gecos', 'dir', 'shell'];
$handle = fopen('/etc/passwd', 'r');
if(!$handle){
throw new \RuntimeException("failed to open /etc/passwd for reading! ".print_r(error_get_last(),true));
}
while ( ($values = fgetcsv($handle, 1000, ':')) !== false ) {
$result[] = array_combine($keys, $values);
}
fclose($handle);
return $result;
}
It returns an array containing all users, formatted like this:
[
[
'name' => 'root',
'passwd' => 'x',
'uid' => '0',
'gid' => '0',
'gecos' => 'root',
'dir' => '/root',
'shell' => '/bin/bash',
],
[
'name' => 'daemon',
'passwd' => 'x',
'uid' => '1',
'gid' => '1',
'gecos' => 'daemon',
'dir' => '/usr/sbin',
'shell' => '/usr/sbin/nologin',
],
...
]
Like Matt S said, that's an incredibly bad idea to allow www-data root access on your server. The slightest compromise through your web applications could allow anyone full control of your system.
A better idea would be to make separate scripts for specific accessions then use SUID permissions. This means, a specific user (in this case, www-data) can make small changes to the system through the execution of scripts. Still not a good idea, though. You may be able to work around it with suPHP but security is still a major concern.
/etc/passwd is readable by anyone, so you should be able to execute your command without having any special rights (unless PHP prevents it?).