Cosicas de Informática
Lo explico para enterarme yo
viernes, 27 de marzo de 2026
Previsualización de correo electrónico que no se ve en el cuerpo del mensaje
viernes, 18 de marzo de 2022
Powershell: buscar una cadena dentro de unos ficheros recursivamente
¿Qué ficheros dentro de este directorio y descendientes contienen una cadena específica?
gci -recurse *.php |sls button |select -first 10
jueves, 17 de marzo de 2022
Jugando a wordle (versión tildes) con powershell
Después de probar un par de palabras en el Wordle de ayer (en versión tildes), y no dar con la palabra correcta, me decidí a tener algo de "ayuda" (la palabra "trampas" es muy fea) a partir de un diccionario.
Dado que tengo un fichero que es un listado de palabras en español, con una palabra por línea, me gustaría aplicar un filtro que me busque las palabras que cumplen determinadas condiciones, como tener una "P" en primera posición, contener una "L" (sin especificar la posición) y que tengan una determinada longitud.
Esto me dará varias opciones (en el mejor de los casos, puede que sólo haya una palabra que cumpla esas condiciones, en cuyo caso esa sería la palabra buscada), y mi idea era jugar alguna de esas palabras. Por eso decía lo de "ayuda" en vez de "trampas", pero vamos, puede llamarse de cualquiera de las dos formas. Lo importante aquí es cómo se hace eso en Powershell.
Mi fichero con la lista de palabras en español se llama words.es.txt.
Wordle con tildes: https://wordle.danielfrg.com/tildes/
Después de probar PLÁTANO, las letras acertadas están en las posiciones 0 (P) y 2 (L).
Otras letras acertadas, aunque estarán en otras posiciones, son la A y la O.
Lo interesante aquí es fijarse en el filtro para poner condiciones, que se pone con el comando "Where-Object" seguido de una expresión entre {llaves}, aunque tiene un alias que me gusta, y es "?".
Así pues, la línea de comando powershell para listar las palabras que cumplen esas condiciones quedaría así:
gc words.es.txt |?{($_.length -eq 8) -and ($_[0] -eq 'p') -and ($_[2] -eq 'l')}|sls a |sls o
Curiosamente, he de filtrar por palabras de longitud 8, ya que palabras con letras especiales (eñes, tildes...) se convierten en dos letras, por ejemplo, "peldaño" y "policía" dan "peldaño" y "policÃa", convirtiendo palabras de 7 letras en 8.
Esto debe ser por tema de codificación diferente entre cmd.exe y powerhsell. Pero eso lo investigaré en otra ocasión.
jueves, 10 de febrero de 2022
Powershell: cómo saber qué procesos utilizan una DLL concreta
Ejecutar en la línea de comandos de Powershell:
tasklist /m msvcrt.dll
Una cosa interesante es que se puede pedir la salida en formato CSV, TABLE y LIST.
tasklist /m msvcrt.dll /fo csv > procesos.csv
viernes, 21 de agosto de 2020
Fecha actual, subselects y registros pasados más recientes en MariaDB
MARIADB: FECHA ACTUAL Y REGISTROS MÁS RECIENTES
Como me ha costado un rato, comparto esto por si otro estuviera en el mismo caso, lo ve más rápido y si no es exactamente lo que busca, igual le vale como punto de partida.
Tengo una base de datos en MariaDB y necesito sacar un registro con la fecha más reciente, por lo que he necesitado la fórmula curdate() para sacar la fecha actual
Como lo que quería era la factura más reciente, anterior a hoy (PROBLEMA: tenemos algunas con fecha futura), he hecho una subconsulta. Primero he seleccionado los datos que quería de la factura y he puesto que la fecha fuera igual a la mayor (más reciente) de las pasadas (en el WHERE de la subconsulta le indico que sea anterior a hoy).
SELECT id_factura, cliente, fecha
FROM facturas
WHERE fecha = (select max(fecha)
from facturas
where fecha < curdate())
Es sencillo, pero me ha dado un poco de quebradero de cabeza porque al usar MAX hay que poner el campo que queramos y el paréntesis pegado a la palabra porque si no da error, así me ha pasado a mí en la consulta SQL. Utilizo un servidor de Hostinger, no sé si este comportamiento será el mismo en todos, pero mejor coger una buena costumbre para evitar fallos similares.
Espero que os pueda servir.
martes, 9 de enero de 2018
Renumerar Secuencias en Oracle (ALTER SEQUENCE)
HY000 - 1 OCIStmtExecute: ORA-00001: restricción única (...) violada
Analizando la estructura de la tabla, veo que esta tiene como clave primaria un campo ID [NUMBER(7,0)], y que la operación de inserción obtiene el siguiente ID a insertar de una secuencia (CREATE SEQUENCE...). Esto me lleva a sospechar que probablemente el siguiente ID a insertar esté coincidiendo con algún ID ya existente en la tabla, cosa extraña tratándose de una secuencia, y que no debería pasar, pero... ¡tantas cosas no deberían pasar y pasan!
Vamos con las secuencias en ORACLE.
Para saber cuál será el siguiente valor que devolverá una secuencia, podemos hacer dos cosas:
1. Función NEXTVAL.
La instrucción
SELECT SEC_MI_SECUENCIA.nextval FROM dual;
=> 3601
nos devuelve el siguiente número de la secuencia. El único "problema" es que esta operación "consume" un valor de esa secuencia. Supongamos que me devuelve el valor 3601. Si vuelvo a ejecutar la operación, devolvería un nuevo valor, 3602 (asumiendo que la secuencia se incrementa en pasos de 1 unidad). Cada vez que se vuelva a ejecutar esa instrucción, estaré "gastando" un número de la secuencia. Así que, si este comportamiento no es deseable, cuidado. Esto puede ocurrir cuando tenemos tablas donde queremos TODOS los registros que se puedan ir insertando, como una tabla de registro de actividad (log), por ejemplo. Si vemos saltos en la secuencia de IDs, no sabemos si es que son "normales" o es que tal vez alguien ha eliminado registros para borrar las huellas de alguna operación.
SELECT SEC_MI_SECUENCIA.nextval FROM dual;
=> 3609
2. Función CURRVAL
Esta alternativa permite consultar el último valor que devolvió la secuencia, pero sin generar uno nuevo. Es decir, por muchas ejecuciones que hagamos, siempre nos va a devolver el mismo valor.select SEC_MI_SECUENCIA.currval from dual;
=> 3609
select SEC_MI_SECUENCIA.currval from dual;
=> 3609
Dicho esto, la solución a mi problema es fácil. Veo en los datos que hay registros con un ID ya coincidente con el próximo que generará la secuencia. Así que obtengo el máximo, que resulta ser
select max(id) from SEC_MI_SECUENCIA;
==> 8596
Así que voy a ALTERAR mi secuencia para que genere números a partir del 8600. Para ello, debo especificar un incremento igual a la diferencia entre el valor actual y el deseado. En mi caso, 8600 - 3609 = 4991. Así que hay que alterar la secuencia DOS VECES: una para poner un incremento de 4991, a continuación obtenemos el siguiente valor, y después volvemos a alterar la secuencia para poner el incremento en 1. Así:
ALTER SEQUENCE SEC_MI_SECUENCIA INCREMENT BY 4991;
SELECT SEC_MI_SECUENCIA.nextval FROM dual;
ALTER SEQUENCE SEC_MI_SECUENCIA INCREMENT BY 1;
Tras esto, ya hemos comprobado que la inserción no da problemas.
Hasta que alguien vuelva a tocar los datos...
jueves, 29 de septiembre de 2016
Recorrer una estructura de directorios descomprimiendo ficheros con clave
Tengo una copia de seguridad que es una réplica de una estructura de carpetas y subcarpetas, con varios ficheros repartidos por todo el árbol de directorios.La copia replica la estructura de carpetas en una unidad externa. Cada fichero se comprime individualmente y se cifra con una contraseña.
Si se trata de recuperar un fichero específico, no hay mucho problema, copio el comprimido a la carpeta de destino y con cualquier utilidad de descompresión lo extraigo manualmente, tecleando la contraseña en el momento necesario.
Pero si quiero restaurar TODA UNA CARPETA Y EL SUBÁRBOL que cuelga de ella, ir extrayendo los ficheros uno a uno no es una opción válida (son muchos), y deseo automatizar esta operación. Para ello, necesitaré una utilidad de línea de comandos que me permita extraer un archivo individual (usaré el 7za, la utilidad de línea de comandos del 7-zip) y un script que me permita ir recorriendo el árbol de directorios uno a uno y ejecutar esta utilidad en cada uno de ellos. También tendré que ir especificando la ruta destino individualmente para cada fichero, que deberé ir construyendo dinámicamente.
En esta ocasión, estoy trabajando en Windows. Tras un rato leyendo las especificaciones del comando FOR (en una ventana de comando basta con escribir "help for"), he aquí la solución al problema planteado:
set z="c:\bin\util\7za.exe"
set d="c:\ruta\destino"
set clave=",,"
for /D /R %%i in (*.* .) do %z% x -r -o%d%%%~pi%%~ni %%i\*.zip -p%clave% -y
::~ for /D /R %%i in (*.*) do @echo de %%~pi a %d%%%~pi%%~ni
No repetiré aquí las especificaciones del comando FOR, para eso está su ayuda, que considero suficiente (aunque un poco falta de claridad, lo reconozco), pero sí daré unas breves notas:
- El /D hace referencia a que trabajaremos con directorios, y /R es para que se haga un recorrido recursivo
- El comando a ejecutar con cada directorio debe ir en la misma línea que el FOR, hasta donde yo sé. Desconozco si se puede dividir la instrucción en varias líneas, lo cual ayudaría bastante a la legibilidad, pero yo no lo he conseguido.
- Las variables de entorno (establecidas en las primeras líneas con SET) se escriben como %VAR% y las variables del FOR llevan un doble símbolo de porcentaje %%VAR por estar dentro de un fichero de script. Si estuviéramos tecleándolas directamente en una ventana de comando se escribiría un único %, así: %VAR.
- Los modificadores introducidos delante del nombre de la variable (en el comando a ejecutar) sirven para extraer parte del nombre del directorio, así ~p significa PATH (sólo la ruta, sin el nombre) ~n significa NOMBRE (nombre del directorio a procesar)
- La línea comentada (la que comienza por ::~ for) se puede utilizar para testear el resultado antes de ejecutarlo de verdad, imprimiendo las rutas a procesar, pero sin ejecutar la operación. Para probarlo, bastaría con descomentar esta línea y comentar la anterior a ella.
En la cláusula in es importante añadir, además de *.*, el punto (.) que hace referencia al directorio actual. Si no se incluye, se procesan todos los directorios que cuelgan del actual, pero no el directorio contenedor. Tampoco pasa nada, siempre podía haber copiado la carpeta a restaurar como única carpeta hija de c:\tmp, pongamos por caso, y ejecutar el script en c:\tmp directamente. Pero incluyendo el punto la solución es más genérica, pues vale tanto si en el directorio actual hay ficheros o si sólo hay directorios.
Como reflexión, pensemos que cuando hacemos una copia de seguridad, no basta con tener los ficheros guardados, sino que debemos tener previsto (¡y probado!) el sistema que utilizaremos para restaurar la información en caso necesario. De nada nos sirve tener una copia de seguridad si, en caso de necesidad, a la hora de restaurarla nos vamos a tener que esperar una eternidad a que la información vuelva a estar disponible, por tener que llevar a cabo un proceso manual o un proceso que, aunque esté automatizado, no responda en un tiempo razonable.
Había una cita conocida que no recuerdo literalmente, pero venía a decir una cosa importantísima, algo así como:
- "No planifiques una estrategia de copias de seguridad, planifica una estrategia de recuperación"
(si alguien sabe el autor y la frase exacta, le estaré agradecido si me lo pone en un comentario).
Ahora es buen momento para probar tu procedimiento de contingencia: ¿cuánto tardarías si tuvieras que restaurar ahora mismo alguna información importante?
jueves, 28 de julio de 2016
La crème de la crème (consultas TOP-N en Oracle)
Pero la cosa cambia un poco si las queremos ordenadas, es decir los mejores resultados, las mejores notas, los mejores salarios, la crème de la crème... (o los peores).
Esto significa sacar las N filas primeras (o mínimas) o las N filas últimas (o máximas), lo que se denomina a veces consultas TOP-N (TOP-N queries, por si queréis buscarlo en inglés).
En el caso de SQLite3 podíamos poner la cláusula LIMIT después del ORDER BY, como se explicaba en aquella entrada. Pero en ORACLE no existe la cláusula LIMIT, así que habrá que hacerlo de alguna otra manera.
En principio, utilizando la columna "mágica" ROWNUM, si hacemos lo siguiente:
SELECT * FROM TABLA WHERE ROWNUM <= 100
estamos obteniendo 100 filas aleatorias. Uno podría pensar que entonces, para obtener las TOP-100 filas bastaría con añadir una condición ORDER BY, tal que así:
SELECT * FROM TABLA WHERE ROWNUM <= 100 ORDER BY CAMPO
Pero eso no funcionaría. Lo que nos estaría dando son 100 filas aleatorias, y una vez seleccionadas (y no antes), nos las mostraría ordenadas por el CAMPO indicado. Pero eso no significa que nos esté dando las 100 filas PRIMERAS (según en el orden indicado) de la tabla. El problema es que el valor de ROWNUM se está asignando justo al seleccionar la fila, y la operación ORDER BY se aplica después.
Así pues, la forma correcta de hacerlo en Oracle es intercalar una SELECT dentro de otra:
SELECT * FROM (la-consulta-original) WHERE ROWNUM <= 100
Es decir, para el caso que nos ocupa:
SELECT * FROM (SELECT * FROM TABLA ORDER BY CAMPO) WHERE ROWNUM <= 100
Ale, problema resuelto. A seguir tecleando.
--
Nota: no confundir ROWNUM con ROWID. Ambas son columnas que asigna ORACLE automáticamente, pero no significan lo mismo.
Si queréis una explicación muy buena y bastante detallada acerca de la diferencia entre ROWNUM y ROWID y cómo utilizar ROWNUM para consultas TOP-N y para resultados paginados, echadle un vistazo a este artículo
http://www.epidataconsulting.com/tikiwiki/tiki-read_article.php?articleId=63
martes, 19 de julio de 2016
Codificación de caracteres (charset) en una BD Oracle
SELECT * FROM NLS_DATABASE_PARAMETERS;
podremos saber no sólo la codificación, sino otros parámetros interesantes como el idioma, los separadores decimales y de miles, el formato de fecha y hora, la versión de la BD, el símbolo de moneda y algunas cosas más...
jueves, 23 de junio de 2016
Sesión de galletitas y galletitas de sesiones
1. Sesiones
El servidor web guardará la información de la sesión en algún sitio, puede ser en ficheros de texto, binarios, en una base de datos... Normalmente, una configuración por defecto de Apache + PHP implica que se guarden en ficheros de texto plano.
Vale. Hasta aquí bien.
2. Cookies
Los servidores pueden usar así esta información para "recordar" las preferencias del usuario, su login automático y un sinfín de cosas más útiles y beneficiosas para el usuario, y algunas otras que no son tan beneficiosas para el usuario (pero sí para ciertos sitios web, especialmente si venden publicidad o si tienen intenciones algo más siniestras), por ejemplo, para rastrear las preferencias y hábitos de navegación.
Los navegadores o, lo que es lo mismo, los usuarios, sabemos que si nos hacemos los estrechos la página no nos va a dar mucha funcionalidad, así que nos ponemos en modo facilón, nos dejamos querer y accedemos fácilmente a que el servidor web nos
El navegador guarda ese paquetito de información, esa cookie, en un fichero pequeño normalmente de texto (también pueden ser binarios) o, algo que se ve cada vez más en las últimas versiones de los navegadores, en bases de datos locales tipo SQLite o similares. En cualquier caso, ficheros en el ordenador del cliente web. O, al menos esto es así en teoría, ya que aquí me surge el primer misterio que mencionaba en la introducción. Pero antes de abordar el misterio, recapitulemos lo que llevamos dicho hasta ahora.
Resumiendo:
- Cookies: pequeños trozos de información que almacena el navegador en ficheros de texto, binarios o BD locales tipo SQLite. Se pueden usar para muchas cosas, una de ellas para almacenar ID de sesión (pero también para rastrear al usuario, conocer preferencias...)
- Sesiones: interacciones entre navegador y servidor web que recuerdan info (estado) del usuario, normalmente usando cookies. La información se almacena en el servidor, ya sea en ficheros de texto (pares nombre:valor), binarios, bases de datos...
3. Vale. ¿Y dónde se almacenan?
3.1 LAS COOKIES
https://support.mozilla.org/es/kb/perfiles-el-lugar-donde-firefox-almacena-tus-contr
que se encuentra dentro de la carpeta %AppData%\Roaming\Mozilla\Firefox\Profiles
Si sólo tienes un perfil de usuario, éste incluye la palabra 'default' en el nombre de la carpeta. En mi caso:
%AppData%\Roaming\Mozilla\Firefox\Profiles\67bl3kpn.default-1443683694326
y, dentro de esta carpeta, hay un archivo cookies.sqlite

El contenido de este archivo se puede ver con cualquier visualizador de ficheros SQLite. A mí me gusta mucho el SQLite Expert (edición Personal). Más adelante le echaremos un vistazo.
En el caso de Chrome, la ruta donde se almacena la BD de cookies es
%LOCALAPPDATA%\Google\Chrome\User Data\Default\Cookies
y el fichero se llama cookies a secas, sin extensión. También lo podemos abrir con SQLite Expert o cualquier otro visor.
Si echamos un vistazo a ambos ficheros, veremos cosas parecidas a esto en el caso de Firefox

y esto en el caso de Chrome
3.2 LAS SESIONES
Si miramos en esa carpeta, veremos las sesiones que se han ido creando en los últimos días:

4. ¿Me puedes poner un ejemplo?
Página 1
01 <?php
02 session_start();
03 ?>
04 <!DOCTYPE html>
05 <html lang="es">
06
07 <head>
08 <meta charset="utf-8" />
09 <title>Sesiones - Página 1</title>
10 </head>
11
12 <body>
13 <?php
14 echo 'Estamos en la sesión [' . session_id() . ']<br />'
15 . 'Nombre: ' . session_name() . '<br />';
16
17 //almacenamos algún dato en esa sesión
18 $_SESSION['mes'] = 'junio';
19 $_SESSION['usuario'] = 'pepe';
20 $_SESSION['flor'] = 'amapola';
21
22 echo '<pre>';
23 print_r($_SESSION);
24 echo '</pre>';
25 echo "<br>Ir a <a href='pagina2.php'>la página 2</a> a consultar las variables de sesión"
26
27 ?>
28
29 </body>
30 </html>
Y esta es la página 2
01 <?php
02 session_start();
03 ?>
04 <!DOCTYPE html>
05 <html lang="es">
06
07 <head>
08 <meta charset="utf-8" />
09 <title>Sesiones</title>
10 </head>
11
12 <body>
13 <?php
14 echo 'Estamos en la sesión [' . session_id() . ']<br />Nombre: ' . session_name() . '<br />';
15
16 //recuperamos los valores almacenados en la sesión
17 echo 'Recuperamos el valor mes=' . $_SESSION['mes'] . '<br />';
18 echo 'Recuperamos el valor usuario=' . $_SESSION['usuario'] . '<br />';
19 echo 'Recuperamos el valor flor=' . $_SESSION['flor'] . '<br />';
20
21 echo '<pre>';
22 print_r($_SESSION);
23 echo '</pre>';
24 ?>
25 </body>
26 </html>
Abramos en el navegador la página 1
Antes de pasar a la página 2, veamos qué ha pasado en los sistemas de archivos del servidor y del cliente.
En el servidor
como se ve, el nombre del archivo coincide con el session_id que hemos visto en la página. Si abrimos ese fichero, su contenido es:
Hmmm... Interesante. Vemos que junto a cada variable aparece un tipo de datos (s=string), su tamaño (5) y su valor ("junio"). Las distintas variables de sesión se separan con pipelines.
Pero es a la hora de ver qué ha pasado con las cookies en el cliente cuando aparece el primer "misterio". En teoría, las cookies se almacenan en la BD SQLite que hemos dicho. Pero, cuando muestro las cookies ordenadas por fecha de acceso, no encuentro la cookie de nombre PHPSESSID, que es el nombre que me devuelve la función session_name(). Esto es lo que consigo ver cuando accedo a la BD y filtro por el dominio 'localhost':
Como tras muchos intentos no he conseguido encontrar esta información en la BD, al final he instalado el plugin Firebug en Firefox, que permite visualizar las cookies y ahí sí que puedo ver la cookie que me interesa, como se puede ver un poco más arriba en la figura 6. También si abro la página 2 con el Firebug activado (F12) puedo ver que la cookie de sesión PHPSESSID es la misma
Aún no he conseguido averiguar dónde se almacena la cookie PHPSESSID, pero es obvio que el navegador la está guardando en algún sitio y a mí me gustaría saber dónde. Vale que con el Firebug puedo inspeccionar la cookie, lo cual me soluciona la papeleta por el momento, pero me quedo un poco mosca con no haber podido encontrar la ubicación exacta de la cookie en el disco.
¿Alguien tiene alguna idea?








